From 823befe4e376bc6916d976cb1eb2d5206c08963f Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Wed, 8 Nov 2023 08:46:40 +0100 Subject: [PATCH] Introduce precompile v2 utils & minor refactoring --- Cargo.lock | 209 ++- Cargo.toml | 2 + bin/collator/Cargo.toml | 2 +- precompiles/assets-erc20/src/lib.rs | 22 +- precompiles/assets-erc20/src/tests.rs | 333 +++-- precompiles/utils_v2/Cargo.toml | 53 + precompiles/utils_v2/macro/Cargo.toml | 33 + .../utils_v2/macro/docs/precompile_macro.md | 199 +++ .../utils_v2/macro/src/derive_codec.rs | 137 ++ .../macro/src/generate_function_selector.rs | 83 ++ precompiles/utils_v2/macro/src/lib.rs | 79 ++ .../utils_v2/macro/src/precompile/attr.rs | 144 ++ .../utils_v2/macro/src/precompile/expand.rs | 527 +++++++ .../utils_v2/macro/src/precompile/mod.rs | 137 ++ .../utils_v2/macro/src/precompile/parse.rs | 628 ++++++++ .../macro/src/precompile_name_from_address.rs | 146 ++ .../compile-fail/derive_codec/empty_struct.rs | 31 + .../derive_codec/empty_struct.stderr | 17 + .../tests/compile-fail/derive_codec/enum.rs | 29 + .../compile-fail/derive_codec/enum.stderr | 5 + .../precompile/codec/arg-dont-impl-codec.rs | 34 + .../codec/arg-dont-impl-codec.stderr | 61 + .../precompile/codec/no-output.rs | 32 + .../precompile/codec/no-output.stderr | 5 + .../codec/output-dont-impl-codec.rs | 34 + .../codec/output-dont-impl-codec.stderr | 21 + .../precompile/codec/output-not-result.rs | 33 + .../precompile/codec/output-not-result.stderr | 7 + .../codec/output-wrong-error-result.rs | 33 + .../codec/output-wrong-error-result.stderr | 13 + .../discriminant/dont-return-option.rs | 38 + .../discriminant/dont-return-option.stderr | 5 + .../precompile/discriminant/missing-fn.rs | 33 + .../precompile/discriminant/missing-fn.stderr | 7 + .../precompile/discriminant/missing-param.rs | 38 + .../discriminant/missing-param.stderr | 5 + .../discriminant/return-incomplete-option.rs | 38 + .../return-incomplete-option.stderr | 5 + .../discriminant/too-many-arguments.rs | 38 + .../discriminant/too-many-arguments.stderr | 5 + .../discriminant/type-mismatch-1.rs | 38 + .../discriminant/type-mismatch-1.stderr | 5 + .../discriminant/type-mismatch-2.rs | 38 + .../discriminant/type-mismatch-2.stderr | 5 + .../fn-modifiers/discriminant-multiple.rs | 34 + .../fn-modifiers/discriminant-multiple.stderr | 5 + .../fn-modifiers/multiple-modifiers.rs | 34 + .../fn-modifiers/multiple-modifiers.stderr | 5 + .../fn-modifiers/pre-check-multiple.rs | 33 + .../fn-modifiers/pre-check-multiple.stderr | 5 + .../compile-fail/precompile/handle/missing.rs | 32 + .../precompile/handle/missing.stderr | 5 + .../precompile/handle/set-missing.rs | 33 + .../precompile/handle/set-missing.stderr | 5 + .../precompile/handle/set-wrong-type.rs | 33 + .../precompile/handle/set-wrong-type.stderr | 5 + .../precompile/handle/wrong-type.rs | 32 + .../precompile/handle/wrong-type.stderr | 5 + .../precompile/pre-check/no-parameter.rs | 37 + .../precompile/pre-check/no-parameter.stderr | 5 + .../pre-check/too-many-parameters.rs | 37 + .../pre-check/too-many-parameters.stderr | 5 + .../precompile/pre-check/wrong-parameter.rs | 37 + .../pre-check/wrong-parameter.stderr | 5 + .../precompile/test-gen/generic-arg.rs | 32 + .../precompile/test-gen/generic-arg.stderr | 11 + .../compile-fail/precompile_name/not_tuple.rs | 25 + .../precompile_name/not_tuple.stderr | 5 + .../macro/tests/expand/precompile.expanded.rs | 364 +++++ .../utils_v2/macro/tests/expand/precompile.rs | 80 ++ .../tests/expand/precompile_name.expanded.rs | 37 + .../macro/tests/expand/precompile_name.rs | 31 + .../tests/expand/precompileset.expanded.rs | 1257 +++++++++++++++++ .../macro/tests/expand/precompileset.rs | 259 ++++ .../tests/expand/returns_tuple.expanded.rs | 108 ++ .../macro/tests/expand/returns_tuple.rs | 31 + .../utils_v2/macro/tests/pass/derive_codec.rs | 82 ++ .../tests/pass/precompile_fn_modifiers.rs | 75 + precompiles/utils_v2/macro/tests/tests.rs | 56 + precompiles/utils_v2/src/evm/costs.rs | 118 ++ precompiles/utils_v2/src/evm/handle.rs | 216 +++ precompiles/utils_v2/src/evm/logs.rs | 109 ++ precompiles/utils_v2/src/evm/mod.rs | 22 + precompiles/utils_v2/src/lib.rs | 83 ++ precompiles/utils_v2/src/precompile_set.rs | 1100 +++++++++++++++ .../utils_v2/src/solidity/codec/bytes.rs | 224 +++ .../utils_v2/src/solidity/codec/mod.rs | 363 +++++ .../utils_v2/src/solidity/codec/native.rs | 383 +++++ precompiles/utils_v2/src/solidity/mod.rs | 29 + precompiles/utils_v2/src/solidity/modifier.rs | 56 + precompiles/utils_v2/src/solidity/revert.rs | 385 +++++ precompiles/utils_v2/src/substrate.rs | 155 ++ precompiles/utils_v2/src/testing/account.rs | 188 +++ precompiles/utils_v2/src/testing/execution.rs | 255 ++++ precompiles/utils_v2/src/testing/handle.rs | 216 +++ precompiles/utils_v2/src/testing/mod.rs | 102 ++ precompiles/utils_v2/src/testing/modifier.rs | 130 ++ precompiles/utils_v2/src/testing/solidity.rs | 375 +++++ .../utils_v2/tests-external/Cargo.toml | 28 + precompiles/utils_v2/tests-external/lib.rs | 433 ++++++ precompiles/utils_v2/tests/solidity_test.sol | 123 ++ runtime/astar/Cargo.toml | 2 +- runtime/astar/src/lib.rs | 2 +- runtime/shibuya/Cargo.toml | 2 +- runtime/shibuya/src/lib.rs | 2 +- runtime/shiden/Cargo.toml | 2 +- runtime/shiden/src/lib.rs | 35 +- 107 files changed, 11132 insertions(+), 238 deletions(-) create mode 100644 precompiles/utils_v2/Cargo.toml create mode 100644 precompiles/utils_v2/macro/Cargo.toml create mode 100644 precompiles/utils_v2/macro/docs/precompile_macro.md create mode 100644 precompiles/utils_v2/macro/src/derive_codec.rs create mode 100644 precompiles/utils_v2/macro/src/generate_function_selector.rs create mode 100644 precompiles/utils_v2/macro/src/lib.rs create mode 100644 precompiles/utils_v2/macro/src/precompile/attr.rs create mode 100644 precompiles/utils_v2/macro/src/precompile/expand.rs create mode 100644 precompiles/utils_v2/macro/src/precompile/mod.rs create mode 100644 precompiles/utils_v2/macro/src/precompile/parse.rs create mode 100644 precompiles/utils_v2/macro/src/precompile_name_from_address.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs create mode 100644 precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr create mode 100644 precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/precompile.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/precompile_name.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/precompileset.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs create mode 100644 precompiles/utils_v2/macro/tests/expand/returns_tuple.rs create mode 100644 precompiles/utils_v2/macro/tests/pass/derive_codec.rs create mode 100644 precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs create mode 100644 precompiles/utils_v2/macro/tests/tests.rs create mode 100644 precompiles/utils_v2/src/evm/costs.rs create mode 100644 precompiles/utils_v2/src/evm/handle.rs create mode 100644 precompiles/utils_v2/src/evm/logs.rs create mode 100644 precompiles/utils_v2/src/evm/mod.rs create mode 100644 precompiles/utils_v2/src/lib.rs create mode 100644 precompiles/utils_v2/src/precompile_set.rs create mode 100644 precompiles/utils_v2/src/solidity/codec/bytes.rs create mode 100644 precompiles/utils_v2/src/solidity/codec/mod.rs create mode 100644 precompiles/utils_v2/src/solidity/codec/native.rs create mode 100644 precompiles/utils_v2/src/solidity/mod.rs create mode 100644 precompiles/utils_v2/src/solidity/modifier.rs create mode 100644 precompiles/utils_v2/src/solidity/revert.rs create mode 100644 precompiles/utils_v2/src/substrate.rs create mode 100644 precompiles/utils_v2/src/testing/account.rs create mode 100644 precompiles/utils_v2/src/testing/execution.rs create mode 100644 precompiles/utils_v2/src/testing/handle.rs create mode 100644 precompiles/utils_v2/src/testing/mod.rs create mode 100644 precompiles/utils_v2/src/testing/modifier.rs create mode 100644 precompiles/utils_v2/src/testing/solidity.rs create mode 100644 precompiles/utils_v2/tests-external/Cargo.toml create mode 100644 precompiles/utils_v2/tests-external/lib.rs create mode 100644 precompiles/utils_v2/tests/solidity_test.sol diff --git a/Cargo.lock b/Cargo.lock index 585296c771..e2cf44a10a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "astar-collator" -version = "5.23.0" +version = "5.24.0" dependencies = [ "astar-primitives", "astar-runtime", @@ -543,7 +543,7 @@ dependencies = [ [[package]] name = "astar-runtime" -version = "5.23.0" +version = "5.24.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -695,7 +695,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -706,7 +706,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -819,6 +819,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] + [[package]] name = "bech32" version = "0.9.1" @@ -864,13 +873,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.10", + "prettyplease 0.2.15", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -1160,6 +1169,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + [[package]] name = "cc" version = "1.0.79" @@ -1343,7 +1358,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2100,7 +2115,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2398,7 +2413,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2415,7 +2430,7 @@ checksum = "81b2dab6991c7ab1572fea8cb049db819b1aeea1e2dac74c0869f244d9f21a7c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2605,6 +2620,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difflib" version = "0.4.0" @@ -2690,7 +2711,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2917,7 +2938,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -2928,7 +2949,7 @@ checksum = "c9838a970f5de399d3070ae1739e131986b2f5dcc223c7423ca0927e3a878522" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -3135,13 +3156,13 @@ dependencies = [ "dunce", "ethers-core", "eyre", - "prettyplease 0.2.10", + "prettyplease 0.2.15", "proc-macro2", "quote", "regex", "serde", "serde_json", - "syn 2.0.25", + "syn 2.0.32", "toml 0.7.6", "walkdir", ] @@ -3159,7 +3180,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -3185,7 +3206,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.25", + "syn 2.0.32", "tempfile", "thiserror", "tiny-keccak", @@ -3419,7 +3440,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -3946,7 +3967,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -4063,7 +4084,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -4075,7 +4096,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -4085,7 +4106,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.43#5e dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -4263,7 +4284,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -6143,6 +6164,21 @@ dependencies = [ "libc", ] +[[package]] +name = "macrotest" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7489ae0986ce45414b7b3122c2e316661343ecf396b206e3e15f07c846616f10" +dependencies = [ + "diff", + "glob", + "prettyplease 0.1.25", + "serde", + "serde_json", + "syn 1.0.109", + "toml 0.5.11", +] + [[package]] name = "maplit" version = "1.0.2" @@ -6972,7 +7008,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -6984,7 +7020,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -7641,7 +7677,7 @@ source = "git+https://github.com/AstarNetwork/substrate?branch=astar-polkadot-v0 dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -8548,7 +8584,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -9082,7 +9118,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -9133,7 +9169,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -10467,6 +10503,52 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "precompile-utils-macro-v2" +version = "0.1.0" +dependencies = [ + "case", + "fp-evm", + "frame-support", + "macrotest", + "num_enum 0.5.11", + "precompile-utils-v2", + "prettyplease 0.2.15", + "proc-macro2", + "quote", + "sp-core-hashing", + "sp-std", + "syn 1.0.109", + "trybuild", +] + +[[package]] +name = "precompile-utils-v2" +version = "0.1.0" +dependencies = [ + "derive_more", + "environmental", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "impl-trait-for-tuples", + "log", + "num_enum 0.5.11", + "pallet-evm", + "parity-scale-codec", + "precompile-utils-macro-v2", + "scale-info", + "serde", + "similar-asserts", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "predicates" version = "2.1.5" @@ -10509,12 +10591,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -10589,7 +10671,7 @@ checksum = "0e99670bafb56b9a106419397343bdbc8b8742c3cc449fec6345f86173f47cd4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -10965,7 +11047,7 @@ checksum = "68bf53dad9b6086826722cdc99140793afd9f62faa14a1ad07eb4f955e7a7216" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -11603,7 +11685,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -12571,7 +12653,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -12901,7 +12983,7 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -13017,7 +13099,7 @@ dependencies = [ [[package]] name = "shibuya-runtime" -version = "5.23.0" +version = "5.24.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -13124,7 +13206,7 @@ dependencies = [ [[package]] name = "shiden-runtime" -version = "5.23.0" +version = "5.24.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -13456,7 +13538,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -13698,7 +13780,7 @@ dependencies = [ "proc-macro2", "quote", "sp-core-hashing", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -13717,7 +13799,7 @@ source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.43#5e dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -13928,7 +14010,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14114,7 +14196,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14316,7 +14398,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14480,9 +14562,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -14579,7 +14661,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14759,7 +14841,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14916,7 +14998,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -14959,7 +15041,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -15127,6 +15209,21 @@ dependencies = [ "zstd 0.12.3+zstd.1.5.2", ] +[[package]] +name = "trybuild" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + [[package]] name = "tt-call" version = "1.0.9" @@ -15160,7 +15257,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.7.3", + "rand 0.8.5", "static_assertions", ] @@ -15409,7 +15506,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -15443,7 +15540,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -16573,7 +16670,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] @@ -16706,7 +16803,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.32", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4593bcfba4..70c72ef58f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ ethers = { version = "2.0.9", default_features = false } # Substrate # (wasm) sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } +sp-core-hashing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } @@ -301,6 +302,7 @@ assets-chain-extension-types = { path = "./chain-extensions/types/assets", defau unified-accounts-chain-extension-types = { path = "./chain-extensions/types/unified-accounts", default-features = false } precompile-utils = { path = "./precompiles/utils", default-features = false } +precompile-utils-v2 = { path = "./precompiles/utils_v2", default-features = false } local-runtime = { path = "./runtime/local", default-features = false } shibuya-runtime = { path = "./runtime/shibuya", default-features = false } diff --git a/bin/collator/Cargo.toml b/bin/collator/Cargo.toml index a6edaf5836..8f93b8e1ec 100644 --- a/bin/collator/Cargo.toml +++ b/bin/collator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-collator" -version = "5.23.0" +version = "5.24.0" description = "Astar collator implementation in Rust." build = "build.rs" default-run = "astar-collator" diff --git a/precompiles/assets-erc20/src/lib.rs b/precompiles/assets-erc20/src/lib.rs index 3c7eb308ad..cc6a70fce2 100644 --- a/precompiles/assets-erc20/src/lib.rs +++ b/precompiles/assets-erc20/src/lib.rs @@ -36,7 +36,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{IsPrecompileResult, PrecompileHandle, PrecompileOutput}; +use fp_evm::{IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput}; use frame_support::traits::fungibles::approvals::Inspect as ApprovalInspect; use frame_support::traits::fungibles::metadata::Inspect as MetadataInspect; use frame_support::traits::fungibles::Inspect; @@ -47,8 +47,8 @@ use frame_support::{ }; use pallet_evm::{AddressMapping, PrecompileSet}; use precompile_utils::{ - keccak256, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, - LogExt, LogsBuilder, PrecompileHandleExt, RuntimeHelper, + keccak256, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, + FunctionModifier, LogExt, LogsBuilder, PrecompileHandleExt, RuntimeHelper, }; use sp_runtime::traits::{Bounded, Zero}; @@ -358,12 +358,13 @@ where input.expect_arguments(2)?; let to: H160 = input.read::
()?.into(); - let amount = input.read::>()?; + let amount: U256 = input.read()?; // Build call with origin. { let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let to = Runtime::AddressMapping::into_account_id(to); + let amount = Self::u256_to_amount(amount)?; // Dispatch call (if enough gas). RuntimeHelper::::try_dispatch( @@ -400,13 +401,14 @@ where let from: H160 = input.read::
()?.into(); let to: H160 = input.read::
()?.into(); - let amount = input.read::>()?; + let amount: U256 = input.read()?; { let caller: Runtime::AccountId = Runtime::AddressMapping::into_account_id(handle.context().caller); let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from); let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to); + let amount = Self::u256_to_amount(amount)?; // If caller is "from", it can spend as much as it wants from its own balance. if caller != from { @@ -539,7 +541,7 @@ where input.expect_arguments(2)?; let beneficiary: H160 = input.read::
()?.into(); - let amount = input.read::>()?; + let amount = Self::u256_to_amount(input.read::()?)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let beneficiary = Runtime::AddressMapping::into_account_id(beneficiary); @@ -566,7 +568,7 @@ where input.expect_arguments(2)?; let who: H160 = input.read::
()?.into(); - let amount = input.read::>()?; + let amount = Self::u256_to_amount(input.read::()?)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let who = Runtime::AddressMapping::into_account_id(who); @@ -584,4 +586,10 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + + fn u256_to_amount(value: U256) -> Result, PrecompileFailure> { + value + .try_into() + .map_err(|_| revert("Error processing amount")) + } } diff --git a/precompiles/assets-erc20/src/tests.rs b/precompiles/assets-erc20/src/tests.rs index 6751a0288c..a7af894de7 100644 --- a/precompiles/assets-erc20/src/tests.rs +++ b/precompiles/assets-erc20/src/tests.rs @@ -204,6 +204,172 @@ fn get_balances_unknown_user() { }); } +#[test] +fn mint_is_ok() { + ExtBuilder::default().build().execute_with(|| { + let asset_id = 0; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id, + Account::Alice.into(), + true, + 1, + )); + + // Sanity check, Bob should be without assets + assert!(Assets::balance(asset_id, &Account::Bob.into()).is_zero()); + + // Mint some assets for Bob + let mint_amount = 7 * 11 * 19; + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(asset_id), + EvmDataWriter::new_with_selector(Action::Mint) + .write(Address(Account::Bob.into())) + .write(U256::from(mint_amount)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // Ensure Bob's asset balance was increased + assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), mint_amount); + }); +} + +#[test] +fn mint_non_admin_is_not_ok() { + ExtBuilder::default().build().execute_with(|| { + let asset_id = 0; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id, + Account::Alice.into(), + true, + 1, + )); + + precompiles() + .prepare_test( + Account::Bob, + Account::AssetId(asset_id), + EvmDataWriter::new_with_selector(Action::Mint) + .write(Address(Account::Bob.into())) + .write(U256::from(42)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); + + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(0u128), + EvmDataWriter::new_with_selector(Action::Mint) + .write(Address(Account::Alice.into())) + .write(U256::from(1) << 128) + .build(), + ) + .execute_reverts(|output| { + from_utf8(&output) + .unwrap() + .contains("Error processing amount") + }); + }); +} + +#[test] +fn burn_is_ok() { + ExtBuilder::default().build().execute_with(|| { + let asset_id = 0; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id, + Account::Alice.into(), + true, + 1, + )); + + // Issue some initial assets for Bob + let init_amount = 123; + assert_ok!(Assets::mint( + RuntimeOrigin::signed(Account::Alice), + asset_id, + Account::Bob.into(), + init_amount, + )); + assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), init_amount); + + // Burn some assets from Bob + let burn_amount = 19; + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(asset_id), + EvmDataWriter::new_with_selector(Action::Burn) + .write(Address(Account::Bob.into())) + .write(U256::from(burn_amount)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // Ensure Bob's asset balance was decreased + assert_eq!( + Assets::balance(asset_id, &Account::Bob.into()), + init_amount - burn_amount + ); + }); +} + +#[test] +fn burn_non_admin_is_not_ok() { + ExtBuilder::default().build().execute_with(|| { + let asset_id = 0; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id, + Account::Alice.into(), + true, + 1, + )); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(Account::Alice), + asset_id, + Account::Bob.into(), + 1000000, + )); + + precompiles() + .prepare_test( + Account::Bob, + Account::AssetId(asset_id), + EvmDataWriter::new_with_selector(Action::Burn) + .write(Address(Account::Bob.into())) + .write(U256::from(42)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); + + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(0u128), + EvmDataWriter::new_with_selector(Action::Burn) + .write(Address(Account::Alice.into())) + .write(U256::from(1) << 128) + .build(), + ) + .execute_reverts(|output| { + from_utf8(&output) + .unwrap() + .contains("Error processing amount") + }); + }); +} + #[test] fn approve() { ExtBuilder::default() @@ -468,6 +634,21 @@ fn transfer_not_enough_founds() { .contains("Dispatched call failed with error: DispatchErrorWithPostInfo") && from_utf8(&output).unwrap().contains("BalanceLow") }); + + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(0u128), + EvmDataWriter::new_with_selector(Action::Transfer) + .write(Address(Account::Charlie.into())) + .write(U256::from(1) << 128) + .build(), + ) + .execute_reverts(|output| { + from_utf8(&output) + .unwrap() + .contains("Error processing amount") + }); }); } @@ -697,6 +878,22 @@ fn transfer_from_above_allowance() { error: Module(ModuleError { index: 2, error: [10, 0, 0, 0], \ message: Some(\"Unapproved\") }) }" }); + + precompiles() + .prepare_test( + Account::Alice, + Account::AssetId(0u128), + EvmDataWriter::new_with_selector(Action::TransferFrom) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .write(U256::from(1) << 128) + .build(), + ) + .execute_reverts(|output| { + from_utf8(&output) + .unwrap() + .contains("Error processing amount") + }); }); } @@ -845,139 +1042,3 @@ fn minimum_balance_is_right() { .execute_returns(EvmDataWriter::new().write(expected_min_balance).build()); }); } - -#[test] -fn mint_is_ok() { - ExtBuilder::default().build().execute_with(|| { - let asset_id = 0; - assert_ok!(Assets::force_create( - RuntimeOrigin::root(), - asset_id, - Account::Alice.into(), - true, - 1, - )); - - // Sanity check, Bob should be without assets - assert!(Assets::balance(asset_id, &Account::Bob.into()).is_zero()); - - // Mint some assets for Bob - let mint_amount = 7 * 11 * 19; - precompiles() - .prepare_test( - Account::Alice, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Mint) - .write(Address(Account::Bob.into())) - .write(U256::from(mint_amount)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // Ensure Bob's asset balance was increased - assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), mint_amount); - }); -} - -#[test] -fn mint_non_admin_is_not_ok() { - ExtBuilder::default().build().execute_with(|| { - let asset_id = 0; - assert_ok!(Assets::force_create( - RuntimeOrigin::root(), - asset_id, - Account::Alice.into(), - true, - 1, - )); - - precompiles() - .prepare_test( - Account::Bob, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Mint) - .write(Address(Account::Bob.into())) - .write(U256::from(42)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); - }); -} - -#[test] -fn burn_is_ok() { - ExtBuilder::default().build().execute_with(|| { - let asset_id = 0; - assert_ok!(Assets::force_create( - RuntimeOrigin::root(), - asset_id, - Account::Alice.into(), - true, - 1, - )); - - // Issue some initial assets for Bob - let init_amount = 123; - assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), - asset_id, - Account::Bob.into(), - init_amount, - )); - assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), init_amount); - - // Burn some assets from Bob - let burn_amount = 19; - precompiles() - .prepare_test( - Account::Alice, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Burn) - .write(Address(Account::Bob.into())) - .write(U256::from(burn_amount)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // Ensure Bob's asset balance was decreased - assert_eq!( - Assets::balance(asset_id, &Account::Bob.into()), - init_amount - burn_amount - ); - }); -} - -#[test] -fn burn_non_admin_is_not_ok() { - ExtBuilder::default().build().execute_with(|| { - let asset_id = 0; - assert_ok!(Assets::force_create( - RuntimeOrigin::root(), - asset_id, - Account::Alice.into(), - true, - 1, - )); - assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), - asset_id, - Account::Bob.into(), - 1000000, - )); - - precompiles() - .prepare_test( - Account::Bob, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Burn) - .write(Address(Account::Bob.into())) - .write(U256::from(42)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); - }); -} diff --git a/precompiles/utils_v2/Cargo.toml b/precompiles/utils_v2/Cargo.toml new file mode 100644 index 0000000000..936fd6194e --- /dev/null +++ b/precompiles/utils_v2/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "precompile-utils-v2" +authors = { workspace = true } +description = "Utils to write EVM precompiles." +edition = "2021" +version = "0.1.0" + +[dependencies] +derive_more = { workspace = true, optional = true } +environmental = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true, optional = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +num_enum = { workspace = true } +scale-info = { workspace = true, optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } +similar-asserts = { workspace = true, optional = true } + +# Moonbeam +precompile-utils-macro-v2 = { path = "macro" } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Frontier +evm = { workspace = true, features = ["with-codec"] } +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } + +[dev-dependencies] +hex-literal = { workspace = true } + +[features] +default = ["std"] +std = [ + "environmental/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "parity-scale-codec/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", +] +testing = ["derive_more", "hex-literal", "scale-info", "serde", "similar-asserts", "std"] diff --git a/precompiles/utils_v2/macro/Cargo.toml b/precompiles/utils_v2/macro/Cargo.toml new file mode 100644 index 0000000000..cc6e820931 --- /dev/null +++ b/precompiles/utils_v2/macro/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "precompile-utils-macro-v2" +authors = { workspace = true } +description = "" +edition = "2021" +version = "0.1.0" + +[lib] +proc-macro = true + +[[test]] +name = "tests" +path = "tests/tests.rs" + +[dependencies] +case = "1.0" +num_enum = { workspace = true } +prettyplease = "0.2.12" +proc-macro2 = "1.0" +quote = "1.0" +sp-core-hashing = { workspace = true } +syn = { version = "1.0", features = ["extra-traits", "fold", "full", "visit"] } + +[dev-dependencies] +macrotest = "1.0.9" +trybuild = "1.0" + +precompile-utils-v2 = { path = "../", features = ["testing"] } + +fp-evm = { workspace = true } +frame-support = { workspace = true } +sp-core-hashing = { workspace = true } +sp-std = { workspace = true } diff --git a/precompiles/utils_v2/macro/docs/precompile_macro.md b/precompiles/utils_v2/macro/docs/precompile_macro.md new file mode 100644 index 0000000000..e72a4e002a --- /dev/null +++ b/precompiles/utils_v2/macro/docs/precompile_macro.md @@ -0,0 +1,199 @@ +# `#[precompile]` procedural macro. + +This procedural macro allows to simplify the implementation of an EVM precompile or precompile set +using an `impl` block with annotations to automatically generate: + +- the implementation of the trait `Precompile` or `PrecompileSet` (exposed by the `fp_evm` crate) +- parsing of the method parameters from Solidity encoding into Rust type, based on the `solidity::Codec` + trait (exposed by the `precompile-utils` crate) +- a test to ensure the types expressed in the Solidity signature match the Rust types in the + implementation. + +## How to use + +Define your precompile type and write an `impl` block that will contain the precompile methods +implementation. This `impl` block can have type parameters and a `where` clause, which will be +reused to generate the `Precompile`/`PrecompileSet` trait implementation and the enum representing +each public function of precompile with its parsed arguments. + +```rust,ignore +pub struct ExemplePrecompile(PhantomData<(R,I)>); + +#[precomile_utils::precompile] +impl ExemplePrecompile +where + R: pallet_evm::Config +{ + #[precompile::public("example(uint32)")] + fn example(handle: &mut impl PrecompileHandle, arg: u32) -> EvmResult { + Ok(arg * 2) + } +} +``` + +The example code above will automatically generate an enum like + +```rust,ignore +#[allow(non_camel_case_types)] +pub enum ExemplePrecompileCall +where + R: pallet_evm::Config +{ + example { + arg: u32 + }, + // + an non constrible variant with a PhantomData<(R,I)> +} +``` + +This enum have the function `parse_call_data` that can parse the calldata, recognize the Solidity +4-bytes selector and parse the appropriate enum variant. + +It will also generate automatically an implementation of `Precompile`/`PrecompileSet` that calls +this function and the content of the variant to its associated function of the `impl` block. + +## Function attributes + +`#[precompile::public("signature")]` allows to declare a function as a public method of the +precompile with the provided Solidity signature. A function can have multiple `public` attributes to +support renamed functions with backward compatibility, however the arguments must have the same +type. It is not allowed to use the exact same signature multiple times. + +The function must take a `&mut impl PrecompileHandle` as parameter, followed by all the parameters +of the Solidity function in the same order. Those parameters types must implement `solidity::Codec`, and +their name should match the one used in the Solidity interface (.sol) while being in `snake_case`, +which will automatically be converted to `camelCase` in revert messages. The function must return an +`EvmResult`, which is an alias of `Result`. This `T` must implement the +`solidity::Codec` trait and must match the return type in the Solidity interface. The macro will +automatically encode it to Solidity format. + +By default those functions are considered non-payable and non-view (can cause state changes). This +can be changed using either `#[precompile::payable]` or `#[precompile::view]`. Only one can be used. + +It is also possible to declare a fallback function using `#[precompile::fallback]`. This function +will be called if the selector is unknown or if the input is less than 4-bytes long (no selector). +This function cannot have any parameter outside of the `PrecompileHandle`. A function can be both +`public` and `fallback`. + +In case some check must be performed before parsing the input, such as forbidding being called from +some address, a function can be annotated with `#[precompile::pre_check]`: + +```rust,ignore +#[precompile::pre_check] +fn pre_check(handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("Perform your check here") +} +``` + +This function cannot have other attributes. + +## PrecompileSet + +By default the macro considers the `impl` block to represent a precompile and this will implement +the `Precompile` trait. If you want to instead implement a precompile set, you must add the +`#[precompile::precompile_set]` to the `impl` block. + +Then, it is necessary to have a function annotated with the `#[precompile::discriminant]` attribute. +This function is called with the **code address**, the address of the precompile. It must return +`None` if this address is not part of the precompile set, or `Some` if it is. The `Some` variants +contains a value of a type of your choice that represents which member of the set this address +corresponds to. For example for our XC20 precompile sets this function returns the asset id +corresponding to this address if it exists. + +Finally, every other function annotated with a `precompile::_` attribute must now take this +discriminant as first parameter, before the `PrecompileHandle`. + +```rust,ignore +pub struct ExemplePrecompileSet(PhantomData); + +#[precompile_utils::precompile] +#[precompile::precompile_set] +impl ExamplePrecompileSet +where + R: pallet_evm::Config +{ + #[precompile::discriminant] + fn discriminant(address: H160) -> Option { + // Replace with your discriminant logic. + Some(match address { + a if a == H160::from(42) => 1 + a if a == H160::from(43) => 2, + _ => return None, + }) + } + + #[precompile::public("example(uint32)")] + fn example(discriminant: u8, handle: &mut impl PrecompileHandle, arg: u32) -> EvmResult { + // Discriminant can be used here. + Ok(arg * discriminant) + } +} +``` + +## Solidity signatures test + +The macro will automatically generate a unit test to ensure that the types expressed in a `public` +attribute matches the Rust parameters of the function, thanks to the `solidity::Codec` trait having the +`solidity_type() -> String` function. + +If any **parsed** argument (discriminant is not concerned) depends on the type parameters of the +`impl` block, the macro will not be able to produce valid code and output an error like: + +```text +error[E0412]: cannot find type `R` in this scope + --> tests/precompile/compile-fail/test/generic-arg.rs:25:63 + | +23 | impl> Precompile { + | - help: you might be missing a type parameter: `` +24 | #[precompile::public("foo(bytes)")] +25 | fn foo(handle: &mut impl PrecompileHandle, arg: BoundedBytes) -> EvmResult { + | ^ not found in this scope +``` + +In this case you need to annotate the `impl` block with the `#[precompile::test_concrete_types(...)]` +attributes. The `...` should be replaced with concrete types for each type parameter, like a mock +runtime. Those types are only used to generate the test and only one set of types can be used. + +```rust,ignore +pub struct ExamplePrecompile(PhantomData<(R, I)>); + +pub struct GetMaxSize(PhantomData<(R, I)>); + +impl Get for GetMaxSize { + fn get() -> u32 { + >::SomeConstant::get() + } +} + +#[precompile_utils::precompile] +#[precompile::test_concrete_types(mock::Runtime, Instance1)] +impl ExamplePrecompile +where + R: pallet_evm::Config + SomeConfig +{ + #[precompile::public("example(bytes)")] + fn example( + handle: &mut impl PrecompileHandle, + data: BoundedBytes>, + ) -> EvmResult { + todo!("Method implementation") + } +} +``` + +## Enum functions + +The generated enums exposes the following public functions: + +- `parse_call_data`: take a `PrecompileHandle` and tries to parse the call data. Returns an + `EvmResult`. It **DOES NOT** execute the code of the annotated `impl` block. +- `supports_selector`: take a selector as a `u32` is returns if this selector is supported by the + precompile(set) as a `bool`. Note that the presence of a fallback function is not taken into + account. +- `selectors`: returns a static array (`&'static [u32]`) of all the supported selectors. +- For each variant/public function `foo`, there is a function `foo_selectors` which returns a static + array of all the supported selectors **for that function**. That can be used to ensure in tests + that some function have a selector that was computed by hand. +- `encode`: take `self` and encodes it in Solidity format. Additionally, `Vec` implements + `From` which simply call encodes. This is useful to write tests as you can construct the + variant you want and it will be encoded to Solidity format for you. diff --git a/precompiles/utils_v2/macro/src/derive_codec.rs b/precompiles/utils_v2/macro/src/derive_codec.rs new file mode 100644 index 0000000000..f4ef4770e6 --- /dev/null +++ b/precompiles/utils_v2/macro/src/derive_codec.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{quote, quote_spanned}; +use syn::{ + parse_macro_input, punctuated::Punctuated, spanned::Spanned, DeriveInput, Ident, LitStr, Path, + PathSegment, PredicateType, TraitBound, TraitBoundModifier, +}; + +pub fn main(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + mut generics, + data, + .. + } = parse_macro_input!(input as DeriveInput); + + let syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(fields), + .. + }) = data + else { + return quote_spanned! { ident.span() => + compile_error!("Codec can only be derived for structs with named fields"); + } + .into(); + }; + let fields = fields.named; + + if fields.is_empty() { + return quote_spanned! { ident.span() => + compile_error!("Codec can only be derived for structs with at least one field"); + } + .into(); + } + + if let Some(unamed_field) = fields.iter().find(|f| f.ident.is_none()) { + return quote_spanned! { unamed_field.ty.span() => + compile_error!("Codec can only be derived for structs with named fields"); + } + .into(); + } + + let fields_ty: Vec<_> = fields.iter().map(|f| &f.ty).collect(); + let fields_ident: Vec<_> = fields + .iter() + .map(|f| f.ident.as_ref().expect("None case checked above")) + .collect(); + let fields_name_lit: Vec<_> = fields_ident + .iter() + .map(|i| LitStr::new(&i.to_string(), i.span())) + .collect(); + + let evm_data_trait_path = { + let mut segments = Punctuated::::new(); + segments.push(Ident::new("precompile_utils", Span::call_site()).into()); + segments.push(Ident::new("solidity", Span::call_site()).into()); + segments.push(Ident::new("Codec", Span::call_site()).into()); + Path { + leading_colon: Some(Default::default()), + segments, + } + }; + let where_clause = generics.make_where_clause(); + + for ty in &fields_ty { + let mut bounds = Punctuated::new(); + bounds.push( + TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: evm_data_trait_path.clone(), + } + .into(), + ); + + where_clause.predicates.push( + PredicateType { + lifetimes: None, + bounded_ty: (*ty).clone(), + colon_token: Default::default(), + bounds, + } + .into(), + ); + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote! { + impl #impl_generics ::precompile_utils::solidity::codec::Codec for #ident #ty_generics + #where_clause { + fn read( + reader: &mut ::precompile_utils::solidity::codec::Reader + ) -> ::precompile_utils::solidity::revert::MayRevert { + use ::precompile_utils::solidity::revert::BacktraceExt as _; + let (#(#fields_ident,)*): (#(#fields_ty,)*) = reader + .read() + .map_in_tuple_to_field(&[#(#fields_name_lit),*])?; + Ok(Self { + #(#fields_ident,)* + }) + } + + fn write(writer: &mut ::precompile_utils::solidity::codec::Writer, value: Self) { + ::precompile_utils::solidity::codec::Codec::write(writer, (#(value.#fields_ident,)*)); + } + + fn has_static_size() -> bool { + <(#(#fields_ty,)*)>::has_static_size() + } + + fn signature() -> String { + <(#(#fields_ty,)*)>::signature() + } + } + } + .into() +} diff --git a/precompiles/utils_v2/macro/src/generate_function_selector.rs b/precompiles/utils_v2/macro/src/generate_function_selector.rs new file mode 100644 index 0000000000..7b0a62e1ce --- /dev/null +++ b/precompiles/utils_v2/macro/src/generate_function_selector.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; + +pub fn main(_: TokenStream, input: TokenStream) -> TokenStream { + let item = parse_macro_input!(input as ItemEnum); + + let ItemEnum { + attrs, + vis, + enum_token, + ident, + variants, + .. + } = item; + + let mut ident_expressions: Vec = vec![]; + let mut variant_expressions: Vec = vec![]; + let mut variant_attrs: Vec> = vec![]; + for variant in variants { + match variant.discriminant { + Some((_, Expr::Lit(ExprLit { lit, .. }))) => { + if let Lit::Str(lit_str) = lit { + let digest = Keccak256::digest(lit_str.value().as_bytes()); + let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]); + ident_expressions.push(variant.ident); + variant_expressions.push(Expr::Lit(ExprLit { + lit: Lit::Verbatim(Literal::u32_suffixed(selector)), + attrs: Default::default(), + })); + variant_attrs.push(variant.attrs); + } else { + return quote_spanned! { + lit.span() => compile_error!("Expected literal string"); + } + .into(); + } + } + Some((_eg, expr)) => { + return quote_spanned! { + expr.span() => compile_error!("Expected literal"); + } + .into() + } + None => { + return quote_spanned! { + variant.span() => compile_error!("Each variant must have a discriminant"); + } + .into() + } + } + } + + (quote! { + #(#attrs)* + #[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] + #[repr(u32)] + #vis #enum_token #ident { + #( + #(#variant_attrs)* + #ident_expressions = #variant_expressions, + )* + } + }) + .into() +} diff --git a/precompiles/utils_v2/macro/src/lib.rs b/precompiles/utils_v2/macro/src/lib.rs new file mode 100644 index 0000000000..0b595ca042 --- /dev/null +++ b/precompiles/utils_v2/macro/src/lib.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![crate_type = "proc-macro"] +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use sp_core_hashing::keccak_256; +use syn::{parse_macro_input, spanned::Spanned, Expr, Ident, ItemType, Lit, LitStr}; + +mod derive_codec; +mod precompile; +mod precompile_name_from_address; + +struct Bytes(Vec); + +impl ::std::fmt::Debug for Bytes { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + let data = &self.0; + write!(f, "[")?; + if !data.is_empty() { + write!(f, "{:#04x}u8", data[0])?; + for unit in data.iter().skip(1) { + write!(f, ", {:#04x}", unit)?; + } + } + write!(f, "]") + } +} + +#[proc_macro] +pub fn keccak256(input: TokenStream) -> TokenStream { + let lit_str = parse_macro_input!(input as LitStr); + + let hash = keccak_256(lit_str.value().as_bytes()); + + let bytes = Bytes(hash.to_vec()); + let eval_str = format!("{:?}", bytes); + let eval_ts: proc_macro2::TokenStream = eval_str.parse().unwrap_or_else(|_| { + panic!( + "Failed to parse the string \"{}\" to TokenStream.", + eval_str + ); + }); + quote!(#eval_ts).into() +} + +#[proc_macro_attribute] +pub fn precompile(attr: TokenStream, input: TokenStream) -> TokenStream { + precompile::main(attr, input) +} + +#[proc_macro_attribute] +pub fn precompile_name_from_address(attr: TokenStream, input: TokenStream) -> TokenStream { + precompile_name_from_address::main(attr, input) +} + +#[proc_macro_derive(Codec)] +pub fn derive_codec(input: TokenStream) -> TokenStream { + derive_codec::main(input) +} diff --git a/precompiles/utils_v2/macro/src/precompile/attr.rs b/precompiles/utils_v2/macro/src/precompile/attr.rs new file mode 100644 index 0000000000..d7c7a459f5 --- /dev/null +++ b/precompiles/utils_v2/macro/src/precompile/attr.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use proc_macro2::Span; +use quote::ToTokens; +use syn::spanned::Spanned; + +pub fn take_attributes(attributes: &mut Vec) -> syn::Result> +where + A: syn::parse::Parse, +{ + let mut output = vec![]; + let pred = |attr: &syn::Attribute| { + attr.path + .segments + .first() + .map_or(false, |segment| segment.ident == "precompile") + }; + + while let Some(index) = attributes.iter().position(pred) { + let attr = attributes.remove(index); + let attr = syn::parse2(attr.into_token_stream())?; + output.push(attr) + } + Ok(output) +} + +/// List of additional token to be used for parsing. +pub mod keyword { + syn::custom_keyword!(precompile); + syn::custom_keyword!(public); + syn::custom_keyword!(fallback); + syn::custom_keyword!(payable); + syn::custom_keyword!(view); + syn::custom_keyword!(discriminant); + syn::custom_keyword!(precompile_set); + syn::custom_keyword!(test_concrete_types); + syn::custom_keyword!(pre_check); +} + +/// Attributes for methods. +pub enum MethodAttr { + Public(Span, syn::LitStr), + Fallback(Span), + Payable(Span), + View(Span), + Discriminant(Span), + PreCheck(Span), +} + +impl syn::parse::Parse for MethodAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + + let lookahead = content.lookahead1(); + + if lookahead.peek(keyword::public) { + let span = content.parse::()?.span(); + + let inner; + syn::parenthesized!(inner in content); + let signature = inner.parse::()?; + + Ok(MethodAttr::Public(span, signature)) + } else if lookahead.peek(keyword::fallback) { + Ok(MethodAttr::Fallback( + content.parse::()?.span(), + )) + } else if lookahead.peek(keyword::payable) { + Ok(MethodAttr::Payable( + content.parse::()?.span(), + )) + } else if lookahead.peek(keyword::view) { + Ok(MethodAttr::View(content.parse::()?.span())) + } else if lookahead.peek(keyword::discriminant) { + Ok(MethodAttr::Discriminant( + content.parse::()?.span(), + )) + } else if lookahead.peek(keyword::pre_check) { + Ok(MethodAttr::PreCheck( + content.parse::()?.span(), + )) + } else { + Err(lookahead.error()) + } + } +} + +/// Attributes for the main impl Block. +pub enum ImplAttr { + PrecompileSet(Span), + TestConcreteTypes(Span, Vec), +} + +impl syn::parse::Parse for ImplAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + + let lookahead = content.lookahead1(); + + if lookahead.peek(keyword::precompile_set) { + Ok(ImplAttr::PrecompileSet( + content.parse::()?.span(), + )) + } else if lookahead.peek(keyword::test_concrete_types) { + let span = content.parse::()?.span(); + + let inner; + syn::parenthesized!(inner in content); + let types = inner.parse_terminated::<_, syn::Token![,]>(syn::Type::parse)?; + + Ok(ImplAttr::TestConcreteTypes( + span, + types.into_iter().collect(), + )) + } else { + Err(lookahead.error()) + } + } +} diff --git a/precompiles/utils_v2/macro/src/precompile/expand.rs b/precompiles/utils_v2/macro/src/precompile/expand.rs new file mode 100644 index 0000000000..aed4fce127 --- /dev/null +++ b/precompiles/utils_v2/macro/src/precompile/expand.rs @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; + +impl Precompile { + /// Main expand function, which expands everything else. + pub fn expand(&self) -> impl ToTokens { + let enum_ = self.expand_enum_decl(); + let enum_impl = self.expand_enum_impl(); + let precomp_impl = self.expand_precompile_impl(); + let test_signature = self.expand_test_solidity_signature(); + + quote! { + #enum_ + #enum_impl + #precomp_impl + #test_signature + } + } + + /// Expands the call enum declaration. + pub fn expand_enum_decl(&self) -> impl ToTokens { + let enum_ident = &self.enum_ident; + let (_impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + + let type_parameters = self.generics.type_params().map(|p| &p.ident); + + let variants: Vec<_> = self.variants_content.keys().collect(); + let idents: Vec> = self + .variants_content + .values() + .map(|v| v.arguments.iter().map(|a| &a.ident).collect()) + .collect(); + let types: Vec> = self + .variants_content + .values() + .map(|v| v.arguments.iter().map(|a| &a.ty).collect()) + .collect(); + + quote!( + #[allow(non_camel_case_types)] + pub enum #enum_ident #ty_generics #where_clause { + #( + #variants { + #( + #idents: #types + ),* + }, + )* + + #[doc(hidden)] + __phantom( + ::core::marker::PhantomData<( #( #type_parameters ),* )>, + ::core::convert::Infallible + ), + } + ) + } + + /// Expands the parse function for each variants. + pub fn expand_variants_parse_fn(&self) -> impl ToTokens { + let span = Span::call_site(); + + let fn_parse = self + .variants_content + .keys() + .map(Self::variant_ident_to_parse_fn); + + let modifier_check = self.variants_content.values().map(|variant| { + let modifier = match variant.modifier { + Modifier::NonPayable => "NonPayable", + Modifier::Payable => "Payable", + Modifier::View => "View", + }; + + let modifier = syn::Ident::new(modifier, span); + + quote!( + use ::precompile_utils::solidity::modifier::FunctionModifier; + use ::precompile_utils::evm::handle::PrecompileHandleExt; + handle.check_function_modifier(FunctionModifier::#modifier)?; + ) + }); + + let variant_parsing = self + .variants_content + .iter() + .map(|(variant_ident, variant)| { + Self::expand_variant_parsing_from_handle(variant_ident, variant) + }); + + quote!( + #( + fn #fn_parse( + handle: &mut impl PrecompileHandle + ) -> ::precompile_utils::EvmResult { + use ::precompile_utils::solidity::revert::InjectBacktrace; + + #modifier_check + #variant_parsing + } + )* + ) + } + + /// Generates the parsing code for a variant, reading the input from the handle and + /// parsing it using Reader. + fn expand_variant_parsing_from_handle( + variant_ident: &syn::Ident, + variant: &Variant, + ) -> impl ToTokens { + if variant.arguments.is_empty() { + quote!( Ok(Self::#variant_ident {})).to_token_stream() + } else { + use case::CaseExt; + + let args_parse = variant.arguments.iter().map(|arg| { + let ident = &arg.ident; + let span = ident.span(); + let name = ident.to_string().to_camel_lowercase(); + + quote_spanned!(span=> #ident: input.read().in_field(#name)?,) + }); + let args_count = variant.arguments.len(); + + quote!( + let mut input = handle.read_after_selector()?; + input.expect_arguments(#args_count)?; + + Ok(Self::#variant_ident { + #(#args_parse)* + }) + ) + .to_token_stream() + } + } + + /// Expands the call enum impl block. + pub fn expand_enum_impl(&self) -> impl ToTokens { + let enum_ident = &self.enum_ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + + let match_selectors = self.selector_to_variant.keys(); + let match_selectors2 = self.selector_to_variant.keys(); + + let variants_parsing = self.expand_variants_parse_fn(); + + let variants_ident2: Vec<_> = self.variants_content.keys().collect(); + let variants_selectors_fn: Vec<_> = self + .variants_content + .keys() + .map(|name| format_ident!("{}_selectors", name)) + .collect(); + let variants_selectors: Vec<_> = self + .variants_content + .values() + .map(|variant| &variant.selectors) + .collect(); + + let variants_list: Vec> = self + .variants_content + .values() + .map(|variant| variant.arguments.iter().map(|arg| &arg.ident).collect()) + .collect(); + + let variants_encode: Vec<_> = self + .variants_content + .values() + .map(Self::expand_variant_encoding) + .collect(); + + let parse_call_data_fn = self.expand_enum_parse_call_data(); + let execute_fn = self.expand_enum_execute_fn(); + + quote!( + impl #impl_generics #enum_ident #ty_generics #where_clause { + #parse_call_data_fn + + #variants_parsing + + #execute_fn + + pub fn supports_selector(selector: u32) -> bool { + match selector { + #( + #match_selectors => true, + )* + _ => false, + } + } + + pub fn selectors() -> &'static [u32] { + &[#( + #match_selectors2 + ),*] + } + + #( + pub fn #variants_selectors_fn() -> &'static [u32] { + &[#( + #variants_selectors + ),*] + } + )* + + pub fn encode(self) -> ::sp_std::vec::Vec { + use ::precompile_utils::solidity::codec::Writer; + match self { + #( + Self::#variants_ident2 { #(#variants_list),* } => { + #variants_encode + }, + )* + Self::__phantom(_, _) => panic!("__phantom variant should not be used"), + } + } + } + + impl #impl_generics From<#enum_ident #ty_generics> for ::sp_std::vec::Vec + #where_clause + { + fn from(a: #enum_ident #ty_generics) -> ::sp_std::vec::Vec { + a.encode() + } + } + ) + } + + /// Expand the execute fn of the enum. + fn expand_enum_execute_fn(&self) -> impl ToTokens { + let impl_type = &self.impl_type; + + let variants_ident: Vec<_> = self.variants_content.keys().collect(); + + let variants_arguments: Vec> = self + .variants_content + .values() + .map(|variant| variant.arguments.iter().map(|arg| &arg.ident).collect()) + .collect(); + + // If there is no precompile set there is no discriminant. + let opt_discriminant_arg = self + .precompile_set_discriminant_type + .as_ref() + .map(|ty| quote!( discriminant: #ty,)); + + let variants_call = self + .variants_content + .iter() + .map(|(variant_ident, variant)| { + let arguments = variant.arguments.iter().map(|arg| &arg.ident); + + let output_span = variant.fn_output.span(); + let opt_discriminant_arg = self + .precompile_set_discriminant_fn + .as_ref() + .map(|_| quote!(discriminant,)); + + let write_output = quote_spanned!(output_span=> + ::precompile_utils::solidity::encode_return_value(output?) + ); + + quote!( + let output = <#impl_type>::#variant_ident( + #opt_discriminant_arg + handle, + #(#arguments),* + ); + #write_output + ) + }); + + quote!( + pub fn execute( + self, + #opt_discriminant_arg + handle: &mut impl PrecompileHandle + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + use ::precompile_utils::solidity::codec::Writer; + use ::fp_evm::{PrecompileOutput, ExitSucceed}; + + let output = match self { + #( + Self::#variants_ident { #(#variants_arguments),* } => { + #variants_call + }, + )* + Self::__phantom(_, _) => panic!("__phantom variant should not be used"), + }; + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output + }) + } + ) + } + + /// Expand how a variant can be Solidity encoded. + fn expand_variant_encoding(variant: &Variant) -> impl ToTokens { + match variant.selectors.first() { + Some(selector) => { + let write_arguments = variant.arguments.iter().map(|arg| { + let ident = &arg.ident; + let span = ident.span(); + quote_spanned!(span=> .write(#ident)) + }); + + quote!( + Writer::new_with_selector(#selector) + #(#write_arguments)* + .build() + ) + .to_token_stream() + } + None => quote!(Default::default()).to_token_stream(), + } + } + + /// Expand the main parsing function that, based on the selector in the + /// input, dispatch the decoding to one of the variants parsing function. + fn expand_enum_parse_call_data(&self) -> impl ToTokens { + let selectors = self.selector_to_variant.keys(); + let parse_fn = self + .selector_to_variant + .values() + .map(Self::variant_ident_to_parse_fn); + + let match_fallback = match &self.fallback_to_variant { + Some(variant) => { + let parse_fn = Self::variant_ident_to_parse_fn(variant); + quote!(_ => Self::#parse_fn(handle),).to_token_stream() + } + None => quote!( + Some(_) => Err(RevertReason::UnknownSelector.into()), + None => Err(RevertReason::read_out_of_bounds("selector").into()), + ) + .to_token_stream(), + }; + + quote!( + pub fn parse_call_data( + handle: &mut impl PrecompileHandle + ) -> ::precompile_utils::EvmResult { + use ::precompile_utils::solidity::revert::RevertReason; + + let input = handle.input(); + + let selector = input.get(0..4).map(|s| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(s); + u32::from_be_bytes(buffer) + }); + + match selector { + #( + Some(#selectors) => Self::#parse_fn(handle), + )* + #match_fallback + } + } + ) + } + + fn variant_ident_to_parse_fn(ident: &syn::Ident) -> syn::Ident { + format_ident!("_parse_{}", ident) + } + + /// Expands the impl of the Precomile(Set) trait. + pub fn expand_precompile_impl(&self) -> impl ToTokens { + let impl_type = &self.impl_type; + let enum_ident = &self.enum_ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + + if let Some(discriminant_fn) = &self.precompile_set_discriminant_fn { + let opt_pre_check = self.pre_check.as_ref().map(|ident| { + let span = ident.span(); + quote_spanned!(span=> + let _: () = <#impl_type>::#ident(discriminant, handle) + .map_err(|err| Some(err))?; + ) + }); + + quote!( + impl #impl_generics ::fp_evm::PrecompileSet for #impl_type #where_clause { + fn execute( + &self, + handle: &mut impl PrecompileHandle + ) -> Option<::precompile_utils::EvmResult<::fp_evm::PrecompileOutput>> { + use ::precompile_utils::precompile_set::DiscriminantResult; + + let discriminant = <#impl_type>::#discriminant_fn( + handle.code_address(), + handle.remaining_gas() + ); + + if let DiscriminantResult::Some(_, cost) | DiscriminantResult::None(cost) = discriminant { + let result = handle.record_cost(cost); + if let Err(e) = result { + return Some(Err(e.into())); + } + } + + let discriminant = match discriminant { + DiscriminantResult::Some(d, _) => d, + DiscriminantResult::None(cost) => return None, + DiscriminantResult::OutOfGas => return Some(Err(ExitError::OutOfGas.into())) + }; + + #opt_pre_check + + Some( + <#enum_ident #ty_generics>::parse_call_data(handle) + .and_then(|call| call.execute(discriminant, handle)) + ) + } + + fn is_precompile(&self, address: H160, gas: u64) -> ::fp_evm::IsPrecompileResult { + <#impl_type>::#discriminant_fn(address, gas).into() + } + } + ) + .to_token_stream() + } else { + let opt_pre_check = self.pre_check.as_ref().map(|ident| { + let span = ident.span(); + quote_spanned!(span=>let _: () = <#impl_type>::#ident(handle)?;) + }); + + quote!( + impl #impl_generics ::fp_evm::Precompile for #impl_type #where_clause { + fn execute( + handle: &mut impl PrecompileHandle + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + #opt_pre_check + + <#enum_ident #ty_generics>::parse_call_data(handle)?.execute(handle) + } + } + ) + .to_token_stream() + } + } + + /// Expands the Solidity signature test. + /// The macro expands an "inner" function in all build profiles, which is + /// then called by a test in test profile. This allows to display errors that occurs in + /// the expansion of the test without having to build in test profile, which is usually + /// related to the use of a type parameter in one of the parsed parameters of a method. + pub fn expand_test_solidity_signature(&self) -> impl ToTokens { + let variant_test: Vec<_> = self + .variants_content + .iter() + .map(|(ident, variant)| { + let span = ident.span(); + + let solidity = &variant.solidity_arguments_type; + let name = ident.to_string(); + let types: Vec<_> = variant.arguments.iter().map(|arg| &arg.ty).collect(); + + quote_spanned!(span=> + assert_eq!( + #solidity, + <(#(#types,)*) as Codec>::signature(), + "{} function signature doesn't match (left: attribute, right: computed \ + from Rust types)", + #name + ); + ) + }) + .collect(); + + let test_name = format_ident!("__{}_test_solidity_signatures", self.impl_ident); + let inner_name = format_ident!("__{}_test_solidity_signatures_inner", self.impl_ident); + + if let Some(test_types) = &self.test_concrete_types { + let (impl_generics, _ty_generics, where_clause) = self.generics.split_for_impl(); + + quote!( + #[allow(non_snake_case)] + pub(crate) fn #inner_name #impl_generics () #where_clause { + use ::precompile_utils::solidity::Codec; + #(#variant_test)* + } + + #[test] + #[allow(non_snake_case)] + fn #test_name() { + #inner_name::< #(#test_types),* >(); + } + ) + .to_token_stream() + } else { + quote!( + #[allow(non_snake_case)] + pub(crate) fn #inner_name() { + use ::precompile_utils::solidity::Codec; + #(#variant_test)* + } + + #[test] + #[allow(non_snake_case)] + fn #test_name() { + #inner_name(); + } + ) + .to_token_stream() + } + } +} diff --git a/precompiles/utils_v2/macro/src/precompile/mod.rs b/precompiles/utils_v2/macro/src/precompile/mod.rs new file mode 100644 index 0000000000..2c8ac1df8e --- /dev/null +++ b/precompiles/utils_v2/macro/src/precompile/mod.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![doc = include_str!("../../docs/precompile_macro.md")] + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use sp_core_hashing::keccak_256; +use std::collections::BTreeMap; +use syn::{parse_macro_input, spanned::Spanned}; + +pub mod attr; +pub mod expand; +pub mod parse; + +pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Macro must be used on `impl` block. + let mut impl_item = parse_macro_input!(item as syn::ItemImpl); + + // We inspect the block to collect all the data we need for the + // expansion, and make various checks. + let precompile = match Precompile::try_from(&mut impl_item) { + Ok(p) => p, + Err(e) => return e.into_compile_error().into(), + }; + + // We generate additional code based on the collected data. + let new_items = precompile.expand(); + let output = quote!( + #impl_item + #new_items + ); + + output.into() +} + +struct Precompile { + /// Impl struct type. + impl_type: syn::Type, + + /// Impl struct ident. + impl_ident: syn::Ident, + + /// New parsing enum ident. + enum_ident: syn::Ident, + + /// Generic part that needs to also be used by the input enum. + generics: syn::Generics, + + /// Which selector corresponds to which variant of the input enum. + selector_to_variant: BTreeMap, + + /// Optional fallback function if no selector matches. + fallback_to_variant: Option, + + /// Describes the content of each variant based on the precompile methods. + variants_content: BTreeMap, + + /// Since being a precompile set implies lots of changes, we must know it early + /// in the form of an attribute on the impl block itself. + tagged_as_precompile_set: bool, + + /// Ident of the function returning the PrecompileSet discriminant. + precompile_set_discriminant_fn: Option, + + /// Type of the PrecompileSet discriminant. + precompile_set_discriminant_type: Option, + + /// When generating the selector test the data types might depend on type parameters. + /// The test thus need to be written using concrete types. + test_concrete_types: Option>, + + /// Ident of a function that performs a check before the call is dispatched to the proper + /// function. + pre_check: Option, +} + +#[derive(Debug, PartialEq, Eq)] +enum Modifier { + NonPayable, + Payable, + View, +} + +#[derive(Debug)] +struct Variant { + /// Description of the arguments of this method, which will also + /// be members of a struct variant. + arguments: Vec, + + /// String extracted from the selector attribute. + /// A unit test will be generated to check that this selector matches + /// the Rust arguments. + /// + /// > solidity::Codec trait allows to generate this string at runtime only. Thus + /// > it is required to write it manually in the selector attribute, and + /// > a unit test is generated to check it matches. + solidity_arguments_type: String, + + /// Modifier of the function. They are all exclusive and defaults to + /// `NonPayable`. + modifier: Modifier, + + /// Selectors of this function to be able to encode back the data. + /// Empty if it only the fallback function. + selectors: Vec, + + /// Output of the variant fn (for better error messages). + fn_output: syn::Type, +} + +#[derive(Debug)] +struct Argument { + /// Identifier of the argument, which will be used in the struct variant. + ident: syn::Ident, + + /// Type of the argument, which will be used in the struct variant and + /// to parse the input. + ty: syn::Type, +} diff --git a/precompiles/utils_v2/macro/src/precompile/parse.rs b/precompiles/utils_v2/macro/src/precompile/parse.rs new file mode 100644 index 0000000000..a41a1ba650 --- /dev/null +++ b/precompiles/utils_v2/macro/src/precompile/parse.rs @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; + +impl Precompile { + /// Try to extract information out of an annotated `impl` block. + pub fn try_from(impl_: &mut syn::ItemImpl) -> syn::Result { + // Extract the name of the type used in the `impl` block. + let impl_ident = Self::extract_impl_ident(impl_)?; + let enum_ident = format_ident!("{}Call", impl_ident); + + // We setup the data collection struct. + let mut precompile = Precompile { + impl_type: impl_.self_ty.as_ref().clone(), + impl_ident, + enum_ident, + generics: impl_.generics.clone(), + selector_to_variant: BTreeMap::new(), + variants_content: BTreeMap::new(), + fallback_to_variant: None, + tagged_as_precompile_set: false, + precompile_set_discriminant_fn: None, + precompile_set_discriminant_type: None, + test_concrete_types: None, + pre_check: None, + }; + + precompile.process_impl_attr(impl_)?; + for mut item in &mut impl_.items { + // We only interact with methods and leave the rest as-is. + if let syn::ImplItem::Method(ref mut method) = &mut item { + precompile.process_method(method)?; + } + } + + // Check constraint of PrecompileSet. + if precompile.tagged_as_precompile_set + && precompile.precompile_set_discriminant_fn.is_none() + { + let msg = "A PrecompileSet must have exactly one function tagged with \ + `#[precompile::discriminant]`"; + return Err(syn::Error::new(Span::call_site(), msg)); + } + + Ok(precompile) + } + + /// Process the attributes used on the `impl` block, which allows to declare + /// if it is a PrecompileSet or not, and to provide concrete types for tests if necessary. + fn process_impl_attr(&mut self, impl_: &mut syn::ItemImpl) -> syn::Result<()> { + let attrs = attr::take_attributes::(&mut impl_.attrs)?; + + for attr in attrs { + match attr { + attr::ImplAttr::PrecompileSet(_) => { + self.tagged_as_precompile_set = true; + } + attr::ImplAttr::TestConcreteTypes(span, types) => { + if types.len() != self.generics.params.len() { + let msg = "The amount of types should match the amount of type parameters \ + of the impl block"; + return Err(syn::Error::new(span, msg)); + } + + if self.test_concrete_types.is_some() { + let msg = "Only one set of types can be provided to generate tests"; + return Err(syn::Error::new(span, msg)); + } + + self.test_concrete_types = Some(types); + } + } + } + + Ok(()) + } + + /// Extract the ident of the type of the `impl` block. + /// This ident is used to generate new idents such as the name of the Call enum and + /// the Solidity selector test. + fn extract_impl_ident(impl_: &syn::ItemImpl) -> syn::Result { + let type_path = match impl_.self_ty.as_ref() { + syn::Type::Path(p) => p, + _ => { + let msg = "The type in the impl block must be a path, like `Precompile` or + `example::Precompile`"; + return Err(syn::Error::new(impl_.self_ty.span(), msg)); + } + }; + + let final_path = type_path.path.segments.last().ok_or_else(|| { + let msg = "The type path must be non empty."; + syn::Error::new(impl_.self_ty.span(), msg) + })?; + + Ok(final_path.ident.clone()) + } + + /// Process a single method, looking for attributes and checking mandatory parameters. + fn process_method(&mut self, method: &mut syn::ImplItemMethod) -> syn::Result<()> { + // Take (remove) all attributes related to this macro. + let attrs = attr::take_attributes::(&mut method.attrs)?; + + // If there are no attributes it is a private function and we ignore it. + if attrs.is_empty() { + return Ok(()); + } + + // A method cannot have modifiers if it isn't a fallback and/or doesn't have a selector. + let mut used = false; + + let method_name = method.sig.ident.clone(); + let mut modifier = Modifier::NonPayable; + let mut solidity_arguments_type: Option = None; + let mut arguments = vec![]; + let mut is_fallback = false; + let mut selectors = vec![]; + let initial_arguments = if self.tagged_as_precompile_set { 2 } else { 1 }; + + // We first look for unique attributes. + if let Some(attr::MethodAttr::Discriminant(span)) = attrs.first() { + let span = *span; + + if attrs.len() != 1 { + let msg = "The discriminant attribute must be the only precompile attribute of \ + a function"; + return Err(syn::Error::new(span, msg)); + } + + return self.parse_discriminant_fn(span, method); + } + + if let Some(attr::MethodAttr::PreCheck(span)) = attrs.first() { + let span = *span; + + if attrs.len() != 1 { + let msg = "The pre_check attribute must be the only precompile attribute of \ + a function"; + return Err(syn::Error::new(span, msg)); + } + + return self.parse_pre_check_fn(span, method); + } + + // We iterate over all attributes of the method. + for attr in attrs { + match attr { + attr::MethodAttr::Discriminant(span) => { + let msg = "The discriminant attribute must be the only precompile \ + attribute of the function"; + return Err(syn::Error::new(span, msg)); + } + attr::MethodAttr::PreCheck(span) => { + let msg = "The pre_check attribute must be the only precompile \ + attribute of the function"; + return Err(syn::Error::new(span, msg)); + } + attr::MethodAttr::Fallback(span) => { + if self.fallback_to_variant.is_some() { + let msg = "A precompile can only have 1 fallback function"; + return Err(syn::Error::new(span, msg)); + } + + self.fallback_to_variant = Some(method_name.clone()); + used = true; + is_fallback = true; + } + attr::MethodAttr::Payable(span) => { + if modifier != Modifier::NonPayable { + let msg = + "A precompile method can have at most one modifier (payable, view)"; + return Err(syn::Error::new(span, msg)); + } + + modifier = Modifier::Payable; + } + attr::MethodAttr::View(span) => { + if modifier != Modifier::NonPayable { + let msg = + "A precompile method can have at most one modifier (payable, view)"; + return Err(syn::Error::new(span, msg)); + } + + modifier = Modifier::View; + } + attr::MethodAttr::Public(_, signature_lit) => { + used = true; + + let selector = self.parse_public_attr( + signature_lit, + &method_name, + &mut solidity_arguments_type, + )?; + selectors.push(selector); + } + } + } + + // A method cannot have attributes without being public or fallback. + if !used { + let msg = + "A precompile method cannot have modifiers without being a fallback or having\ + a `public` attribute"; + return Err(syn::Error::new(method.span(), msg)); + } + + // We forbid type parameters. + if let Some(param) = method.sig.generics.params.first() { + let msg = "Exposed precompile methods cannot have type parameters"; + return Err(syn::Error::new(param.span(), msg)); + } + + // Fallback method cannot have custom parameters. + if is_fallback { + if let Some(input) = method.sig.inputs.iter().nth(initial_arguments) { + let msg = if self.tagged_as_precompile_set { + "Fallback methods cannot take any parameter outside of the discriminant and \ + PrecompileHandle" + } else { + "Fallback methods cannot take any parameter outside of the PrecompileHandle" + }; + + return Err(syn::Error::new(input.span(), msg)); + } + } + + let mut method_inputs = method.sig.inputs.iter(); + + // We check the first parameters of the method. + // If this is a PrecompileSet it will look for a discriminant. + // Then for all precompile(set)s it will look for the PrecompileHandle. + // We take them from the iterator such that we are only left with the + // custom arguments. + self.check_initial_parameters(&mut method_inputs, method.sig.span())?; + + // We go through each parameter to collect each name and type that will be used to + // generate the input enum and parse the call data. + for input in method_inputs { + let input = match input { + syn::FnArg::Typed(t) => t, + _ => { + // I don't think it is possible to encounter this error since a self receiver + // seems to only be possible in the first position which is checked in + // `check_initial_parameters`. + let msg = "Exposed precompile methods cannot have a `self` parameter"; + return Err(syn::Error::new(input.span(), msg)); + } + }; + + let msg = "Parameter must be of the form `name: Type`, optionally prefixed by `mut`"; + let ident = match input.pat.as_ref() { + syn::Pat::Ident(pat) => { + if pat.by_ref.is_some() || pat.subpat.is_some() { + return Err(syn::Error::new(pat.span(), msg)); + } + + pat.ident.clone() + } + _ => { + return Err(syn::Error::new(input.pat.span(), msg)); + } + }; + let ty = input.ty.as_ref().clone(); + self.check_type_parameter_usage(&ty)?; + + arguments.push(Argument { ident, ty }) + } + + // Function output. + let output_type = match &method.sig.output { + syn::ReturnType::Type(_, t) => t, + _ => { + let msg = "A precompile method must have a return type of `EvmResult<_>` (exposed \ + by `precompile_utils`)"; + return Err(syn::Error::new(method.sig.span(), msg)); + } + }; + + // We insert the collected data in self. + if self + .variants_content + .insert( + method_name.clone(), + Variant { + arguments, + solidity_arguments_type: solidity_arguments_type.unwrap_or(String::from("()")), + modifier, + selectors, + fn_output: output_type.as_ref().clone(), + }, + ) + .is_some() + { + let msg = "Duplicate method name"; + return Err(syn::Error::new(method_name.span(), msg)); + } + + Ok(()) + } + + /// Check the initial parameters of most methods of a Precompile(Set). + fn check_initial_parameters<'a>( + &mut self, + method_inputs: &mut impl Iterator, + method_span: Span, + ) -> syn::Result<()> { + // Discriminant input + if self.tagged_as_precompile_set { + let input = match method_inputs.next() { + Some(a) => a, + None => { + let msg = "PrecompileSet methods must have at least 2 parameters (the \ + precompile instance discriminant and the PrecompileHandle)"; + return Err(syn::Error::new(method_span, msg)); + } + }; + + let input = match input { + syn::FnArg::Typed(a) => a, + _ => { + let msg = "self is not allowed in precompile methods"; + return Err(syn::Error::new(input.span(), msg)); + } + }; + + let input_type = input.ty.as_ref(); + + self.try_register_discriminant_type(input_type)?; + } + + // Precompile handle input + { + let input = match method_inputs.next() { + Some(a) => a, + None => { + let msg = if self.tagged_as_precompile_set { + "PrecompileSet methods must have at least 2 parameters (the precompile \ + instance discriminant and the PrecompileHandle)" + } else { + "Precompile methods must have at least 1 parameter (the PrecompileHandle)" + }; + + return Err(syn::Error::new(method_span, msg)); + } + }; + + let input = match input { + syn::FnArg::Typed(a) => a, + _ => { + let msg = "self is not allowed in precompile methods"; + return Err(syn::Error::new(input.span(), msg)); + } + }; + + let input_type = input.ty.as_ref(); + + if !is_same_type(input_type, &syn::parse_quote! {&mut impl PrecompileHandle}) { + let msg = "This parameter must have type `&mut impl PrecompileHandle`"; + return Err(syn::Error::new(input_type.span(), msg)); + } + } + + Ok(()) + } + + /// Records the type of the discriminant and ensure they all have the same type. + fn try_register_discriminant_type(&mut self, ty: &syn::Type) -> syn::Result<()> { + if let Some(known_type) = &self.precompile_set_discriminant_type { + if !is_same_type(known_type, ty) { + let msg = format!( + "All discriminants must have the same type (found {} before)", + known_type.to_token_stream() + ); + return Err(syn::Error::new(ty.span(), msg)); + } + } else { + self.precompile_set_discriminant_type = Some(ty.clone()); + } + + Ok(()) + } + + /// Process the discriminant function. + fn parse_discriminant_fn( + &mut self, + span: Span, + method: &syn::ImplItemMethod, + ) -> syn::Result<()> { + if !self.tagged_as_precompile_set { + let msg = "The impl block must be tagged with `#[precompile::precompile_set]` for + the discriminant attribute to be used"; + return Err(syn::Error::new(span, msg)); + } + + if self.precompile_set_discriminant_fn.is_some() { + let msg = "A PrecompileSet can only have 1 discriminant function"; + return Err(syn::Error::new(span, msg)); + } + + let span = method.sig.span(); + + if method.sig.inputs.len() != 2 { + let msg = "The discriminant function must only take code address (H160) and \ + remaining gas (u64) as parameters."; + return Err(syn::Error::new(span, msg)); + } + + let msg = "The discriminant function must return an DiscriminantResult<_> (no type alias)"; + + let return_type = match &method.sig.output { + syn::ReturnType::Type(_, t) => t.as_ref(), + _ => return Err(syn::Error::new(span, msg)), + }; + + let return_path = match return_type { + syn::Type::Path(p) => p, + _ => return Err(syn::Error::new(span, msg)), + }; + + if return_path.qself.is_some() { + return Err(syn::Error::new(span, msg)); + } + + let return_path = &return_path.path; + + if return_path.leading_colon.is_some() || return_path.segments.len() != 1 { + return Err(syn::Error::new(span, msg)); + } + + let return_segment = &return_path.segments[0]; + + if return_segment.ident != "DiscriminantResult" { + return Err(syn::Error::new(return_segment.ident.span(), msg)); + } + + let result_arguments = match &return_segment.arguments { + syn::PathArguments::AngleBracketed(args) => args, + _ => return Err(syn::Error::new(return_segment.ident.span(), msg)), + }; + + if result_arguments.args.len() != 1 { + let msg = "DiscriminantResult type should only have 1 type argument"; + return Err(syn::Error::new(result_arguments.args.span(), msg)); + } + + let discriminant_type: &syn::Type = match &result_arguments.args[0] { + syn::GenericArgument::Type(t) => t, + _ => return Err(syn::Error::new(result_arguments.args.span(), msg)), + }; + + self.try_register_discriminant_type(discriminant_type)?; + + self.precompile_set_discriminant_fn = Some(method.sig.ident.clone()); + + Ok(()) + } + + /// Process the pre_check function. + fn parse_pre_check_fn(&mut self, span: Span, method: &syn::ImplItemMethod) -> syn::Result<()> { + if self.pre_check.is_some() { + let msg = "A Precompile can only have 1 pre_check function"; + return Err(syn::Error::new(span, msg)); + } + + let span = method.sig.span(); + + let mut method_inputs = method.sig.inputs.iter(); + + self.check_initial_parameters(&mut method_inputs, span)?; + + if method_inputs.next().is_some() { + let msg = if self.tagged_as_precompile_set { + "PrecompileSet pre_check method must have exactly 2 parameters (the precompile \ + instance discriminant and the PrecompileHandle)" + } else { + "Precompile pre_check method must have exactly 1 parameter (the \ + PrecompileHandle)" + }; + + return Err(syn::Error::new(span, msg)); + } + + self.pre_check = Some(method.sig.ident.clone()); + + Ok(()) + } + + /// Process a `public` attribute on a method. + fn parse_public_attr( + &mut self, + signature_lit: syn::LitStr, + method_name: &syn::Ident, + solidity_arguments_type: &mut Option, + ) -> syn::Result { + let signature = signature_lit.value(); + // Split signature to get arguments type. + let split: Vec<_> = signature.splitn(2, '(').collect(); + if split.len() != 2 { + let msg = "Selector must have form \"foo(arg1,arg2,...)\""; + return Err(syn::Error::new(signature_lit.span(), msg)); + } + + let local_args_type = format!("({}", split[1]); // add back initial parenthesis + + // If there are multiple public attributes we check that they all have + // the same type. + if let Some(ref args_type) = solidity_arguments_type { + if args_type != &local_args_type { + let msg = "Method cannot have selectors with different types."; + return Err(syn::Error::new(signature_lit.span(), msg)); + } + } else { + *solidity_arguments_type = Some(local_args_type); + } + + // Compute the 4-bytes selector. + let digest = keccak_256(signature.as_bytes()); + let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]); + + if let Some(previous) = self + .selector_to_variant + .insert(selector, method_name.clone()) + { + let msg = format!("Selector collision with method {previous}"); + return Err(syn::Error::new(signature_lit.span(), msg)); + } + + Ok(selector) + } + + /// Check that the provided type doesn't depend on one of the type parameters of the + /// precompile. Check is skipped if `test_concrete_types` attribute is used. + fn check_type_parameter_usage(&self, ty: &syn::Type) -> syn::Result<()> { + if self.test_concrete_types.is_some() { + return Ok(()); + } + + const ERR_MESSAGE: &str = + "impl type parameter is used in functions arguments. Arguments should not have a type +depending on a type parameter, unless it is a length bound for BoundedBytes, +BoundedString or alike, which doesn't affect the Solidity type. + +In that case, you must add a #[precompile::test_concrete_types(...)] attribute on the impl +block to provide concrete types that will be used to run the automatically generated tests +ensuring the Solidity function signatures are correct."; + + match ty { + syn::Type::Array(syn::TypeArray { elem, .. }) + | syn::Type::Group(syn::TypeGroup { elem, .. }) + | syn::Type::Paren(syn::TypeParen { elem, .. }) + | syn::Type::Reference(syn::TypeReference { elem, .. }) + | syn::Type::Ptr(syn::TypePtr { elem, .. }) + | syn::Type::Slice(syn::TypeSlice { elem, .. }) => { + self.check_type_parameter_usage(elem)? + } + + syn::Type::Path(syn::TypePath { + path: syn::Path { segments, .. }, + .. + }) => { + let impl_params: Vec<_> = self + .generics + .params + .iter() + .filter_map(|param| match param { + syn::GenericParam::Type(syn::TypeParam { ident, .. }) => Some(ident), + _ => None, + }) + .collect(); + + for segment in segments { + if impl_params.contains(&&segment.ident) { + return Err(syn::Error::new(segment.ident.span(), ERR_MESSAGE)); + } + + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + let types = args.args.iter().filter_map(|arg| match arg { + syn::GenericArgument::Type(ty) + | syn::GenericArgument::Binding(syn::Binding { ty, .. }) => Some(ty), + _ => None, + }); + + for ty in types { + self.check_type_parameter_usage(ty)?; + } + } + } + } + syn::Type::Tuple(tuple) => { + for ty in tuple.elems.iter() { + self.check_type_parameter_usage(ty)?; + } + } + // BareFn => very unlikely this appear as parameter + // ImplTrait => will cause other errors, it must be a concrete type + // TypeInfer => it must be explicit concrete types since it ends up in enum fields + // Macro => Cannot check easily + // Never => Function will not be callable. + ty => println!("Skipping type parameter check for non supported kind of type: {ty:?}"), + } + + Ok(()) + } +} + +/// Helper to check 2 types are equal. +/// Having a function with explicit type annotation helps type inference at callsite, +/// which have trouble if `==` is used inline. +fn is_same_type(a: &syn::Type, b: &syn::Type) -> bool { + a == b +} diff --git a/precompiles/utils_v2/macro/src/precompile_name_from_address.rs b/precompiles/utils_v2/macro/src/precompile_name_from_address.rs new file mode 100644 index 0000000000..50da96e44a --- /dev/null +++ b/precompiles/utils_v2/macro/src/precompile_name_from_address.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use syn::{GenericArgument, Type}; + +pub fn main(_: TokenStream, input: TokenStream) -> TokenStream { + let item = parse_macro_input!(input as ItemType); + + let ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } = item; + + if let Type::Tuple(ref type_tuple) = *ty { + let variants: Vec<(Ident, u64)> = type_tuple + .elems + .iter() + .filter_map(extract_precompile_name_and_prefix) + .collect(); + + let ident_expressions: Vec<&Ident> = variants.iter().map(|(ident, _)| ident).collect(); + let variant_expressions: Vec<&u64> = variants.iter().map(|(_, id)| id).collect(); + + (quote! { + #(#attrs)* + #vis #type_token #ident #generics #eq_token #ty #semi_token + + #[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive, Debug)] + #[repr(u64)] + pub enum PrecompileName { + #( + #ident_expressions = #variant_expressions, + )* + } + + impl PrecompileName { + pub fn from_address(address: sp_core::H160) -> Option { + let _u64 = address.to_low_u64_be(); + if address == sp_core::H160::from_low_u64_be(_u64) { + use num_enum::TryFromPrimitive; + Self::try_from_primitive(_u64).ok() + } else { + None + } + } + } + }) + .into() + } else { + quote_spanned! { + ty.span() => compile_error!("Expected tuple"); + } + .into() + } +} + +fn extract_precompile_name_and_prefix(type_: &Type) -> Option<(Ident, u64)> { + match type_ { + Type::Path(type_path) => { + if let Some(path_segment) = type_path.path.segments.last() { + match path_segment.ident.to_string().as_ref() { + "PrecompileAt" => { + extract_precompile_name_and_prefix_for_precompile_at(path_segment) + } + _ => None, + } + } else { + None + } + } + _ => None, + } +} + +fn extract_precompile_name_and_prefix_for_precompile_at( + path_segment: &syn::PathSegment, +) -> Option<(Ident, u64)> { + if let syn::PathArguments::AngleBracketed(generics) = &path_segment.arguments { + let mut iter = generics.args.iter(); + if let ( + Some(GenericArgument::Type(Type::Path(type_path_1))), + Some(GenericArgument::Type(Type::Path(type_path_2))), + ) = (iter.next(), iter.next()) + { + if let (Some(path_segment_1), Some(path_segment_2)) = ( + type_path_1.path.segments.last(), + type_path_2.path.segments.last(), + ) { + if let syn::PathArguments::AngleBracketed(generics_) = &path_segment_1.arguments { + if let Some(GenericArgument::Const(Expr::Lit(lit))) = generics_.args.first() { + if let Lit::Int(int) = &lit.lit { + if let Ok(precompile_id) = int.base10_parse() { + if &path_segment_2.ident.to_string() == "CollectivePrecompile" { + if let Some(instance_ident) = + precompile_instance_ident(path_segment_2) + { + return Some((instance_ident, precompile_id)); + } + } else { + return Some((path_segment_2.ident.clone(), precompile_id)); + } + } + } + } + } + } + } + } + + None +} + +fn precompile_instance_ident(path_segment: &syn::PathSegment) -> Option { + if let syn::PathArguments::AngleBracketed(generics_) = &path_segment.arguments { + if let Some(GenericArgument::Type(Type::Path(instance_type_path))) = generics_.args.last() { + if let Some(instance_type) = instance_type_path.path.segments.last() { + return Some(instance_type.ident.clone()); + } + } + } + + None +} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs new file mode 100644 index 0000000000..86bfbcaa32 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use precompile_utils::prelude::*; + +#[derive(solidity::Codec)] +struct Empty1; + +#[derive(solidity::Codec)] +struct Empty2 {} + +#[derive(solidity::Codec)] +struct Empty3(); + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr new file mode 100644 index 0000000000..8c0a9d8bae --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr @@ -0,0 +1,17 @@ +error: Codec can only be derived for structs with named fields + --> tests/compile-fail/derive_codec/empty_struct.rs:20:8 + | +20 | struct Empty1; + | ^^^^^^ + +error: Codec can only be derived for structs with at least one field + --> tests/compile-fail/derive_codec/empty_struct.rs:23:8 + | +23 | struct Empty2 {} + | ^^^^^^ + +error: Codec can only be derived for structs with named fields + --> tests/compile-fail/derive_codec/empty_struct.rs:26:8 + | +26 | struct Empty3 (); + | ^^^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs new file mode 100644 index 0000000000..76eb6740ee --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use precompile_utils::prelude::*; + +#[derive(solidity::Codec)] +enum Test { + One, + Two(u8), + Three { test: u16 }, +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr new file mode 100644 index 0000000000..42a65d4a17 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr @@ -0,0 +1,5 @@ +error: Codec can only be derived for structs with named fields + --> tests/compile-fail/derive_codec/enum.rs:20:6 + | +20 | enum Test { + | ^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs new file mode 100644 index 0000000000..4fea235ac7 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; +use precompile_utils::EvmResult; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(test: &mut impl PrecompileHandle, arg: String) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr new file mode 100644 index 0000000000..1e87e3c771 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr @@ -0,0 +1,61 @@ +error[E0277]: the trait bound `String: Codec` is not satisfied + --> tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs:26:43 + | +26 | fn foo(test: &mut impl PrecompileHandle, arg: String) -> EvmResult { + | ^^^ the trait `Codec` is not implemented for `String` + | + = help: the following other types implement trait `Codec`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others +note: required by a bound in `Reader::<'inner>::read` + --> $WORKSPACE/precompiles/utils/src/solidity/codec/mod.rs + | + | pub fn read(&mut self) -> MayRevert { + | ^^^^^ required by this bound in `Reader::<'inner>::read` + +error[E0277]: the trait bound `String: Codec` is not satisfied + --> tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs:26:43 + | +26 | fn foo(test: &mut impl PrecompileHandle, arg: String) -> EvmResult { + | ^^^ the trait `Codec` is not implemented for `String` + | + = help: the following other types implement trait `Codec`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others +note: required by a bound in `precompile_utils::solidity::codec::Writer::write` + --> $WORKSPACE/precompiles/utils/src/solidity/codec/mod.rs + | + | pub fn write(mut self, value: T) -> Self { + | ^^^^^ required by this bound in `Writer::write` + +error[E0277]: the trait bound `String: Codec` is not satisfied + --> tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs:26:5 + | +26 | fn foo(test: &mut impl PrecompileHandle, arg: String) -> EvmResult { + | ^^^ the trait `Codec` is not implemented for `String` + | + = help: the following other types implement trait `Codec`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others + = note: required for `(String,)` to implement `Codec` diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs new file mode 100644 index 0000000000..a721ada56f --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(test: &mut impl PrecompileHandle) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr new file mode 100644 index 0000000000..7a2758d0f5 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr @@ -0,0 +1,5 @@ +error: A precompile method must have a return type of `EvmResult<_>` (exposed by `precompile_utils`) + --> tests/compile-fail/precompile/codec/no-output.rs:24:2 + | +24 | fn foo(test: &mut impl PrecompileHandle) { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs new file mode 100644 index 0000000000..ada7b05afa --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; +use precompile_utils::EvmResult; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr new file mode 100644 index 0000000000..52ba67e654 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `String: Codec` is not satisfied + --> tests/compile-fail/precompile/codec/output-dont-impl-codec.rs:26:46 + | +26 | fn foo(test: &mut impl PrecompileHandle) -> EvmResult { + | ^^^^^^^^^ the trait `Codec` is not implemented for `String` + | + = help: the following other types implement trait `Codec`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others +note: required by a bound in `encode_arguments` + --> $WORKSPACE/precompiles/utils/src/solidity/codec/mod.rs + | + | pub fn encode_arguments(value: T) -> Vec { + | ^^^^^ required by this bound in `encode_arguments` diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs new file mode 100644 index 0000000000..278cffe68c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(test: &mut impl PrecompileHandle) -> String { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr new file mode 100644 index 0000000000..c104ae8fb3 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr @@ -0,0 +1,7 @@ +error[E0277]: the `?` operator can only be applied to values that implement `Try` + --> tests/compile-fail/precompile/codec/output-not-result.rs:25:46 + | +25 | fn foo(test: &mut impl PrecompileHandle) -> String { + | ^^^^^^ the `?` operator cannot be applied to type `String` + | + = help: the trait `Try` is not implemented for `String` diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs new file mode 100644 index 0000000000..d5d7c31a2c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(test: &mut impl PrecompileHandle) -> Result<(), String> { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr new file mode 100644 index 0000000000..fa1fc8f71c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr @@ -0,0 +1,13 @@ +error[E0277]: `?` couldn't convert the error to `PrecompileFailure` + --> tests/compile-fail/precompile/codec/output-wrong-error-result.rs:25:51 + | +25 | fn foo(test: &mut impl PrecompileHandle) -> Result<(), String> { + | ^ the trait `From` is not implemented for `PrecompileFailure` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = help: the following other types implement trait `From`: + > + > + > + > + = note: required for `Result` to implement `FromResidual>` diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs new file mode 100644 index 0000000000..5c7d5fe26b --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::discriminant] + fn discriminant(address: H160) -> u32 { + 42 + } + + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr new file mode 100644 index 0000000000..66d90708f0 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr @@ -0,0 +1,5 @@ +error: The discriminant function must return an Option<_> (no type alias) + --> tests/compile-fail/precompile/discriminant/dont-return-option.rs:25:36 + | +25 | fn discriminant(address: H160) -> u32 { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs new file mode 100644 index 0000000000..0a27ed2bd4 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::public("foo()")] + fn foo(_discriminant: u32, handle: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr new file mode 100644 index 0000000000..b24b8dddcb --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr @@ -0,0 +1,7 @@ +error: A PrecompileSet must have exactly one function tagged with `#[precompile::discriminant]` + --> tests/compile-fail/precompile/discriminant/missing-fn.rs:21:1 + | +21 | #[precompile_utils_macro::precompile] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `precompile_utils_macro::precompile` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs new file mode 100644 index 0000000000..0447b2031a --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::discriminant] + fn discriminant() -> Option { + Some(42) + } + + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr new file mode 100644 index 0000000000..3026b5ce42 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr @@ -0,0 +1,5 @@ +error: The discriminant function must only take the code address (H160) as parameter. + --> tests/compile-fail/precompile/discriminant/missing-param.rs:25:2 + | +25 | fn discriminant() -> Option { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs new file mode 100644 index 0000000000..41f577bbc2 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::discriminant] + fn discriminant(address: H160) -> Option { + None + } + + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr new file mode 100644 index 0000000000..dc6db9f600 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr @@ -0,0 +1,5 @@ +error: The discriminant function must return an Option<_> (no type alias) + --> tests/compile-fail/precompile/discriminant/return-incomplete-option.rs:25:36 + | +25 | fn discriminant(address: H160) -> Option { + | ^^^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs new file mode 100644 index 0000000000..bbc2e1fe5f --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::discriminant] + fn discriminant(address: H160, other: u32) -> Option { + Some(42) + } + + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr new file mode 100644 index 0000000000..a1b6e87865 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr @@ -0,0 +1,5 @@ +error: The discriminant function must only take the code address (H160) as parameter. + --> tests/compile-fail/precompile/discriminant/too-many-arguments.rs:25:2 + | +25 | fn discriminant(address: H160, other: u32) -> Option { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs new file mode 100644 index 0000000000..620a0a98da --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::discriminant] + fn discriminant(address: H160) -> Option { + Some(42) + } + + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr new file mode 100644 index 0000000000..8d999769e4 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr @@ -0,0 +1,5 @@ +error: All discriminants must have the same type (found u64 before) + --> tests/compile-fail/precompile/discriminant/type-mismatch-1.rs:30:24 + | +30 | fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs new file mode 100644 index 0000000000..e0b2970863 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::public("foo()")] + fn foo(_discriminant: u32, test: &mut impl PrecompileHandle) -> EvmResult { + todo!() + } + + #[precompile::discriminant] + fn discriminant(address: H160) -> Option { + Some(42) + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr new file mode 100644 index 0000000000..d5ed6750af --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr @@ -0,0 +1,5 @@ +error: All discriminants must have the same type (found u32 before) + --> tests/compile-fail/precompile/discriminant/type-mismatch-2.rs:30:43 + | +30 | fn discriminant(address: H160) -> Option { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs new file mode 100644 index 0000000000..ebe1bafcea --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct PrecompileSet(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl PrecompileSet { + #[precompile::discriminant] + #[precompile::view] + fn foo(address: H160) -> Option { + None + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr new file mode 100644 index 0000000000..93f4fb2617 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr @@ -0,0 +1,5 @@ +error: The discriminant attribute must be the only precompile attribute of a function + --> tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs:24:16 + | +24 | #[precompile::discriminant] + | ^^^^^^^^^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs new file mode 100644 index 0000000000..25db0a521c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + #[precompile::view] + #[precompile::payable] + fn foo(_handle: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr new file mode 100644 index 0000000000..678199ab8b --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr @@ -0,0 +1,5 @@ +error: A precompile method can have at most one modifier (payable, view) + --> tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs:25:16 + | +25 | #[precompile::payable] + | ^^^^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs new file mode 100644 index 0000000000..31015637cc --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::pre_check] + #[precompile::view] + fn foo(handle: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr new file mode 100644 index 0000000000..7f96f8b568 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr @@ -0,0 +1,5 @@ +error: The pre_check attribute must be the only precompile attribute of a function + --> tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs:23:16 + | +23 | #[precompile::pre_check] + | ^^^^^^^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs new file mode 100644 index 0000000000..81770e7b5f --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo() { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr new file mode 100644 index 0000000000..2760a71988 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr @@ -0,0 +1,5 @@ +error: Precompile methods must have at least 1 parameter (the PrecompileHandle) + --> tests/compile-fail/precompile/handle/missing.rs:24:2 + | +24 | fn foo() { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs new file mode 100644 index 0000000000..8700c2742d --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::public("foo()")] + fn foo(_: u32) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr new file mode 100644 index 0000000000..8f0a10d8ff --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr @@ -0,0 +1,5 @@ +error: PrecompileSet methods must have at least 2 parameters (the precompile instance discriminant and the PrecompileHandle) + --> tests/compile-fail/precompile/handle/set-missing.rs:25:2 + | +25 | fn foo(_: u32) { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs new file mode 100644 index 0000000000..d571768bfa --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl Precompile { + #[precompile::public("foo()")] + fn foo(_discriminant: u32, _handle: u32) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr new file mode 100644 index 0000000000..bf62c1381c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr @@ -0,0 +1,5 @@ +error: This parameter must have type `&mut impl PrecompileHandle` + --> tests/compile-fail/precompile/handle/set-wrong-type.rs:25:38 + | +25 | fn foo(_discriminant: u32, _handle: u32) { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs new file mode 100644 index 0000000000..01e1fb0f97 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::public("foo()")] + fn foo(_handle: u32) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr new file mode 100644 index 0000000000..12e06e4889 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr @@ -0,0 +1,5 @@ +error: This parameter must have type `&mut impl PrecompileHandle` + --> tests/compile-fail/precompile/handle/wrong-type.rs:24:18 + | +24 | fn foo(_handle: u32) { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs new file mode 100644 index 0000000000..7530add4dd --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::pre_check] + fn pre_check() { + todo!() + } + + #[precompile::public("foo()")] + fn foo(_handle: &mut impl PrecompileHandle) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr new file mode 100644 index 0000000000..406806c51c --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr @@ -0,0 +1,5 @@ +error: Precompile methods must have at least 1 parameter (the PrecompileHandle) + --> tests/compile-fail/precompile/pre-check/no-parameter.rs:24:2 + | +24 | fn pre_check() { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs new file mode 100644 index 0000000000..742c1d2c6f --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::pre_check] + fn pre_check(_: &mut impl PrecompileHandle, _: u32) { + todo!() + } + + #[precompile::public("foo()")] + fn foo(_handle: &mut impl PrecompileHandle) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr new file mode 100644 index 0000000000..474b5c9dcd --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr @@ -0,0 +1,5 @@ +error: Precompile pre_check method must have exactly 1 parameter (the PrecompileHandle) + --> tests/compile-fail/precompile/pre-check/too-many-parameters.rs:24:2 + | +24 | fn pre_check(_: &mut impl PrecompileHandle, _: u32) { + | ^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs new file mode 100644 index 0000000000..42b63886da --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl Precompile { + #[precompile::pre_check] + fn pre_check(_: u32) { + todo!() + } + + #[precompile::public("foo()")] + fn foo(_handle: &mut impl PrecompileHandle) { + todo!() + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr new file mode 100644 index 0000000000..ea95fb0de8 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr @@ -0,0 +1,5 @@ +error: This parameter must have type `&mut impl PrecompileHandle` + --> tests/compile-fail/precompile/pre-check/wrong-parameter.rs:24:18 + | +24 | fn pre_check(_: u32) { + | ^^^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs new file mode 100644 index 0000000000..a5615e8fda --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; + +pub struct Precompile(PhantomData); + +#[precompile_utils_macro::precompile] +impl> Precompile { + #[precompile::public("foo(bytes)")] + fn foo(handle: &mut impl PrecompileHandle, arg: BoundedBytes) -> EvmResult { + Ok(()) + } +} + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr new file mode 100644 index 0000000000..8b4daeb35f --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr @@ -0,0 +1,11 @@ +error: impl type parameter is used in functions arguments. Arguments should not have a type + depending on a type parameter, unless it is a length bound for BoundedBytes, + BoundedString or alike, which doesn't affect the Solidity type. + + In that case, you must add a #[precompile::test_concrete_types(...)] attribute on the impl + block to provide concrete types that will be used to run the automatically generated tests + ensuring the Solidity function signatures are correct. + --> tests/compile-fail/precompile/test-gen/generic-arg.rs:24:63 + | +24 | fn foo(handle: &mut impl PrecompileHandle, arg: BoundedBytes) -> EvmResult { + | ^ diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs b/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs new file mode 100644 index 0000000000..e328bfc092 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +struct Dummy; + +#[precompile_utils_macro::precompile_name_from_address] +type Precompiles = Dummy; + +fn main() {} diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr b/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr new file mode 100644 index 0000000000..343443a248 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr @@ -0,0 +1,5 @@ +error: Expected tuple + --> tests/compile-fail/precompile_name/not_tuple.rs:20:20 + | +20 | type Precompiles = Dummy; + | ^^^^^ diff --git a/precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs b/precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs new file mode 100644 index 0000000000..3153ec9d06 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs @@ -0,0 +1,364 @@ +use core::marker::PhantomData; +use frame_support::pallet_prelude::{ConstU32, Get}; +use precompile_utils::{prelude::*, EvmResult}; +use sp_core::{H160, U256}; +struct BatchPrecompile(PhantomData); +type GetCallDataLimit = ConstU32<42>; +type GetArrayLimit = ConstU32<42>; +impl BatchPrecompile +where + Runtime: Get, +{ + fn pre_check(handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("pre_check") + )) + } + fn batch_some( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("batch_some") + )) + } + fn batch_some_until_failure( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("batch_some_until_failure") + )) + } + fn batch_all( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("batch_all") + )) + } + fn fallback(handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("fallback") + )) + } +} +#[allow(non_camel_case_types)] +pub enum BatchPrecompileCall +where + Runtime: Get, +{ + batch_all { + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + }, + batch_some { + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + }, + batch_some_until_failure { + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + }, + fallback {}, + #[doc(hidden)] + __phantom( + ::core::marker::PhantomData<(Runtime)>, + ::core::convert::Infallible, + ), +} +impl BatchPrecompileCall +where + Runtime: Get, +{ + pub fn parse_call_data( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::solidity::revert::RevertReason; + let input = handle.input(); + let selector = input.get(0..4).map(|s| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(s); + u32::from_be_bytes(buffer) + }); + match selector { + Some(2044677020u32) => Self::_parse_batch_some(handle), + Some(2531431096u32) => Self::_parse_batch_all(handle), + Some(3473183175u32) => Self::_parse_batch_some_until_failure(handle), + _ => Self::_parse_fallback(handle), + } + } + fn _parse_batch_all(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(4usize)?; + Ok(Self::batch_all { + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + call_data: input.read().in_field("callData")?, + gas_limit: input.read().in_field("gasLimit")?, + }) + } + fn _parse_batch_some( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(4usize)?; + Ok(Self::batch_some { + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + call_data: input.read().in_field("callData")?, + gas_limit: input.read().in_field("gasLimit")?, + }) + } + fn _parse_batch_some_until_failure( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(4usize)?; + Ok(Self::batch_some_until_failure { + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + call_data: input.read().in_field("callData")?, + gas_limit: input.read().in_field("gasLimit")?, + }) + } + fn _parse_fallback(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::fallback {}) + } + pub fn execute( + self, + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + use fp_evm::{ExitSucceed, PrecompileOutput}; + use precompile_utils::solidity::codec::Writer; + let output = match self { + Self::batch_all { + to, + value, + call_data, + gas_limit, + } => { + let output = + >::batch_all(handle, to, value, call_data, gas_limit); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::batch_some { + to, + value, + call_data, + gas_limit, + } => { + let output = + >::batch_some(handle, to, value, call_data, gas_limit); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::batch_some_until_failure { + to, + value, + call_data, + gas_limit, + } => { + let output = >::batch_some_until_failure( + handle, to, value, call_data, gas_limit, + ); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::fallback {} => { + let output = >::fallback(handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + }; + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output, + }) + } + pub fn supports_selector(selector: u32) -> bool { + match selector { + 2044677020u32 => true, + 2531431096u32 => true, + 3473183175u32 => true, + _ => false, + } + } + pub fn selectors() -> &'static [u32] { + &[2044677020u32, 2531431096u32, 3473183175u32] + } + pub fn batch_all_selectors() -> &'static [u32] { + &[2531431096u32] + } + pub fn batch_some_selectors() -> &'static [u32] { + &[2044677020u32] + } + pub fn batch_some_until_failure_selectors() -> &'static [u32] { + &[3473183175u32] + } + pub fn fallback_selectors() -> &'static [u32] { + &[] + } + pub fn encode(self) -> ::sp_std::vec::Vec { + use precompile_utils::solidity::codec::Writer; + match self { + Self::batch_all { + to, + value, + call_data, + gas_limit, + } => Writer::new_with_selector(2531431096u32) + .write(to) + .write(value) + .write(call_data) + .write(gas_limit) + .build(), + Self::batch_some { + to, + value, + call_data, + gas_limit, + } => Writer::new_with_selector(2044677020u32) + .write(to) + .write(value) + .write(call_data) + .write(gas_limit) + .build(), + Self::batch_some_until_failure { + to, + value, + call_data, + gas_limit, + } => Writer::new_with_selector(3473183175u32) + .write(to) + .write(value) + .write(call_data) + .write(gas_limit) + .build(), + Self::fallback {} => Default::default(), + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + } + } +} +impl From> for ::sp_std::vec::Vec +where + Runtime: Get, +{ + fn from(a: BatchPrecompileCall) -> ::sp_std::vec::Vec { + a.encode() + } +} +impl ::fp_evm::Precompile for BatchPrecompile +where + Runtime: Get, +{ + fn execute( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + let _: () = >::pre_check(handle)?; + >::parse_call_data(handle)?.execute(handle) + } +} +#[allow(non_snake_case)] +pub(crate) fn __BatchPrecompile_test_solidity_signatures_inner() { + use precompile_utils::solidity::Codec; + match ( + &"(address[],uint256[],bytes[],uint64[])", + &<( + BoundedVec, + BoundedVec, + BoundedVec, GetArrayLimit>, + BoundedVec, + ) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "batch_all" ), ), ); + } + } + }; + match ( + &"(address[],uint256[],bytes[],uint64[])", + &<( + BoundedVec, + BoundedVec, + BoundedVec, GetArrayLimit>, + BoundedVec, + ) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "batch_some" ), ), ); + } + } + }; + match ( + &"(address[],uint256[],bytes[],uint64[])", + &<( + BoundedVec, + BoundedVec, + BoundedVec, GetArrayLimit>, + BoundedVec, + ) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "batch_some_until_failure" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "fallback" ), ), ); + } + } + }; +} diff --git a/precompiles/utils_v2/macro/tests/expand/precompile.rs b/precompiles/utils_v2/macro/tests/expand/precompile.rs new file mode 100644 index 0000000000..6cb99baed6 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompile.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use frame_support::pallet_prelude::{ConstU32, Get}; +use precompile_utils::{prelude::*, EvmResult}; +use sp_core::{H160, U256}; + +// Based on Batch with stripped code. + +struct BatchPrecompile(PhantomData); + +type GetCallDataLimit = ConstU32<42>; +type GetArrayLimit = ConstU32<42>; + +#[precompile_utils_macro::precompile] +impl BatchPrecompile +where + Runtime: Get, +{ + #[precompile::pre_check] + fn pre_check(handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("pre_check") + } + + #[precompile::public("batchSome(address[],uint256[],bytes[],uint64[])")] + fn batch_some( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + todo!("batch_some") + } + + #[precompile::public("batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])")] + fn batch_some_until_failure( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + todo!("batch_some_until_failure") + } + + #[precompile::public("batchAll(address[],uint256[],bytes[],uint64[])")] + fn batch_all( + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + todo!("batch_all") + } + + // additional function to check fallback + #[precompile::fallback] + fn fallback(handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("fallback") + } +} diff --git a/precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs b/precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs new file mode 100644 index 0000000000..ebe51ecade --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs @@ -0,0 +1,37 @@ +struct PrecompileAt(PhantomData<(T, U, V)>); +struct AddressU64; +struct FooPrecompile(PhantomData); +struct BarPrecompile(PhantomData<(R, S)>); +struct MockCheck; +type Precompiles = ( + PrecompileAt, FooPrecompile>, + PrecompileAt, BarPrecompile, (MockCheck, MockCheck)>, +); +#[repr(u64)] +pub enum PrecompileName { + FooPrecompile = 1u64, + BarPrecompile = 2u64, +} +#[automatically_derived] +impl ::core::fmt::Debug for PrecompileName { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str( + f, + match self { + PrecompileName::FooPrecompile => "FooPrecompile", + PrecompileName::BarPrecompile => "BarPrecompile", + }, + ) + } +} +impl PrecompileName { + pub fn from_address(address: sp_core::H160) -> Option { + let _u64 = address.to_low_u64_be(); + if address == sp_core::H160::from_low_u64_be(_u64) { + use num_enum::TryFromPrimitive; + Self::try_from_primitive(_u64).ok() + } else { + None + } + } +} diff --git a/precompiles/utils_v2/macro/tests/expand/precompile_name.rs b/precompiles/utils_v2/macro/tests/expand/precompile_name.rs new file mode 100644 index 0000000000..5c6a6cb11d --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompile_name.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Few mock structs to check the macro. +struct PrecompileAt(PhantomData<(T, U, V)>); +struct AddressU64; +struct FooPrecompile(PhantomData); +struct BarPrecompile(PhantomData<(R, S)>); +struct MockCheck; + +#[precompile_utils_macro::precompile_name_from_address] +type Precompiles = ( + PrecompileAt, FooPrecompile>, + PrecompileAt, BarPrecompile, (MockCheck, MockCheck)>, +); diff --git a/precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs b/precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs new file mode 100644 index 0000000000..d3750bb6bc --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs @@ -0,0 +1,1257 @@ +use core::marker::PhantomData; +use precompile_utils::{prelude::*, testing::PrecompileTesterExt, EvmResult}; +use sp_core::H160; +struct PrecompileSet(PhantomData); +type Discriminant = u32; +type GetAssetsStringLimit = R; +type MockRuntime = ConstU32<42>; +impl PrecompileSet +where + Runtime: Get, +{ + /// PrecompileSet discrimiant. Allows to knows if the address maps to an asset id, + /// and if this is the case which one. + fn discriminant(address: H160) -> Option { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("discriminant") + )) + } + fn total_supply(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("total_supply") + )) + } + fn balance_of( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + who: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("balance_of") + )) + } + fn allowance( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + spender: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("allowance") + )) + } + fn approve( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + spender: Address, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("approve") + )) + } + fn approve_inner( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: H160, + spender: H160, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("approve_inner") + )) + } + fn transfer( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + to: Address, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("transfer") + )) + } + fn transfer_from( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + from: Address, + to: Address, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("transfer_from") + )) + } + fn name( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("name") + )) + } + fn symbol( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("symbol") + )) + } + fn decimals(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("decimals") + )) + } + fn mint( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + to: Address, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("mint") + )) + } + fn burn( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + from: Address, + value: U256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("burn") + )) + } + fn freeze( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + account: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("freeze") + )) + } + fn thaw( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + account: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("thaw") + )) + } + fn freeze_asset(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("freeze_asset") + )) + } + fn thaw_asset(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("thaw_asset") + )) + } + fn transfer_ownership( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("transfer_ownership") + )) + } + fn set_team( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + issuer: Address, + admin: Address, + freezer: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("set_team") + )) + } + fn set_metadata( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + name: BoundedString>, + symbol: BoundedString>, + decimals: u8, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("set_metadata") + )) + } + fn clear_metadata( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("clear_metadata") + )) + } + fn eip2612_permit( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + spender: Address, + value: U256, + deadline: U256, + v: u8, + r: H256, + s: H256, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("eip2612_permit") + )) + } + fn eip2612_nonces( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("eip2612_nonces") + )) + } + fn eip2612_domain_separator( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("eip2612_domain_separator") + )) + } +} +#[allow(non_camel_case_types)] +pub enum PrecompileSetCall +where + Runtime: Get, +{ + allowance { + owner: Address, + spender: Address, + }, + approve { + spender: Address, + value: U256, + }, + balance_of { + who: Address, + }, + burn { + from: Address, + value: U256, + }, + clear_metadata {}, + decimals {}, + eip2612_domain_separator {}, + eip2612_nonces { + owner: Address, + }, + eip2612_permit { + owner: Address, + spender: Address, + value: U256, + deadline: U256, + v: u8, + r: H256, + s: H256, + }, + freeze { + account: Address, + }, + freeze_asset {}, + mint { + to: Address, + value: U256, + }, + name {}, + set_metadata { + name: BoundedString>, + symbol: BoundedString>, + decimals: u8, + }, + set_team { + issuer: Address, + admin: Address, + freezer: Address, + }, + symbol {}, + thaw { + account: Address, + }, + thaw_asset {}, + total_supply {}, + transfer { + to: Address, + value: U256, + }, + transfer_from { + from: Address, + to: Address, + value: U256, + }, + transfer_ownership { + owner: Address, + }, + #[doc(hidden)] + __phantom( + ::core::marker::PhantomData<(Runtime)>, + ::core::convert::Infallible, + ), +} +impl PrecompileSetCall +where + Runtime: Get, +{ + pub fn parse_call_data( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::solidity::revert::RevertReason; + let input = handle.input(); + let selector = input.get(0..4).map(|s| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(s); + u32::from_be_bytes(buffer) + }); + match selector { + Some(117300739u32) => Self::_parse_name(handle), + Some(157198259u32) => Self::_parse_approve(handle), + Some(404098525u32) => Self::_parse_total_supply(handle), + Some(484305945u32) => Self::_parse_thaw_asset(handle), + Some(599290589u32) => Self::_parse_transfer_from(handle), + Some(826074471u32) => Self::_parse_decimals(handle), + Some(910484757u32) => Self::_parse_eip2612_domain_separator(handle), + Some(936559348u32) => Self::_parse_set_metadata(handle), + Some(1086394137u32) => Self::_parse_mint(handle), + Some(1374431959u32) => Self::_parse_thaw_asset(handle), + Some(1587675670u32) => Self::_parse_thaw(handle), + Some(1804030401u32) => Self::_parse_freeze_asset(handle), + Some(1889567281u32) => Self::_parse_balance_of(handle), + Some(2127478272u32) => Self::_parse_eip2612_nonces(handle), + Some(2367676207u32) => Self::_parse_freeze(handle), + Some(2514000705u32) => Self::_parse_symbol(handle), + Some(2646777772u32) => Self::_parse_burn(handle), + Some(2835717307u32) => Self::_parse_transfer(handle), + Some(3352902745u32) => Self::_parse_set_team(handle), + Some(3552201630u32) => Self::_parse_clear_metadata(handle), + Some(3566436177u32) => Self::_parse_freeze_asset(handle), + Some(3573918927u32) => Self::_parse_eip2612_permit(handle), + Some(3714247998u32) => Self::_parse_allowance(handle), + Some(3999121892u32) => Self::_parse_set_metadata(handle), + Some(4021736498u32) => Self::_parse_clear_metadata(handle), + Some(4030008324u32) => Self::_parse_transfer_ownership(handle), + Some(4076725131u32) => Self::_parse_transfer_ownership(handle), + Some(4173303445u32) => Self::_parse_set_team(handle), + Some(_) => Err(RevertReason::UnknownSelector.into()), + None => Err(RevertReason::read_out_of_bounds("selector").into()), + } + } + fn _parse_allowance(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(2usize)?; + Ok(Self::allowance { + owner: input.read().in_field("owner")?, + spender: input.read().in_field("spender")?, + }) + } + fn _parse_approve(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(2usize)?; + Ok(Self::approve { + spender: input.read().in_field("spender")?, + value: input.read().in_field("value")?, + }) + } + fn _parse_balance_of( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(1usize)?; + Ok(Self::balance_of { + who: input.read().in_field("who")?, + }) + } + fn _parse_burn(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(2usize)?; + Ok(Self::burn { + from: input.read().in_field("from")?, + value: input.read().in_field("value")?, + }) + } + fn _parse_clear_metadata( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::clear_metadata {}) + } + fn _parse_decimals(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::decimals {}) + } + fn _parse_eip2612_domain_separator( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::View)?; + Ok(Self::eip2612_domain_separator {}) + } + fn _parse_eip2612_nonces( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::View)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(1usize)?; + Ok(Self::eip2612_nonces { + owner: input.read().in_field("owner")?, + }) + } + fn _parse_eip2612_permit( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(7usize)?; + Ok(Self::eip2612_permit { + owner: input.read().in_field("owner")?, + spender: input.read().in_field("spender")?, + value: input.read().in_field("value")?, + deadline: input.read().in_field("deadline")?, + v: input.read().in_field("v")?, + r: input.read().in_field("r")?, + s: input.read().in_field("s")?, + }) + } + fn _parse_freeze(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(1usize)?; + Ok(Self::freeze { + account: input.read().in_field("account")?, + }) + } + fn _parse_freeze_asset( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::freeze_asset {}) + } + fn _parse_mint(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(2usize)?; + Ok(Self::mint { + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + }) + } + fn _parse_name(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::name {}) + } + fn _parse_set_metadata( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(3usize)?; + Ok(Self::set_metadata { + name: input.read().in_field("name")?, + symbol: input.read().in_field("symbol")?, + decimals: input.read().in_field("decimals")?, + }) + } + fn _parse_set_team(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(3usize)?; + Ok(Self::set_team { + issuer: input.read().in_field("issuer")?, + admin: input.read().in_field("admin")?, + freezer: input.read().in_field("freezer")?, + }) + } + fn _parse_symbol(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::symbol {}) + } + fn _parse_thaw(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(1usize)?; + Ok(Self::thaw { + account: input.read().in_field("account")?, + }) + } + fn _parse_thaw_asset( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::thaw_asset {}) + } + fn _parse_total_supply( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::total_supply {}) + } + fn _parse_transfer(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(2usize)?; + Ok(Self::transfer { + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + }) + } + fn _parse_transfer_from( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(3usize)?; + Ok(Self::transfer_from { + from: input.read().in_field("from")?, + to: input.read().in_field("to")?, + value: input.read().in_field("value")?, + }) + } + fn _parse_transfer_ownership( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + let mut input = handle.read_after_selector()?; + input.expect_arguments(1usize)?; + Ok(Self::transfer_ownership { + owner: input.read().in_field("owner")?, + }) + } + pub fn execute( + self, + discriminant: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + use fp_evm::{ExitSucceed, PrecompileOutput}; + use precompile_utils::solidity::codec::Writer; + let output = match self { + Self::allowance { owner, spender } => { + let output = + >::allowance(discriminant, handle, owner, spender); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::approve { spender, value } => { + let output = + >::approve(discriminant, handle, spender, value); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::balance_of { who } => { + let output = >::balance_of(discriminant, handle, who); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::burn { from, value } => { + let output = >::burn(discriminant, handle, from, value); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::clear_metadata {} => { + let output = >::clear_metadata(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::decimals {} => { + let output = >::decimals(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::eip2612_domain_separator {} => { + let output = + >::eip2612_domain_separator(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::eip2612_nonces { owner } => { + let output = >::eip2612_nonces(discriminant, handle, owner); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::eip2612_permit { + owner, + spender, + value, + deadline, + v, + r, + s, + } => { + let output = >::eip2612_permit( + discriminant, + handle, + owner, + spender, + value, + deadline, + v, + r, + s, + ); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::freeze { account } => { + let output = >::freeze(discriminant, handle, account); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::freeze_asset {} => { + let output = >::freeze_asset(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::mint { to, value } => { + let output = >::mint(discriminant, handle, to, value); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::name {} => { + let output = >::name(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::set_metadata { + name, + symbol, + decimals, + } => { + let output = >::set_metadata( + discriminant, + handle, + name, + symbol, + decimals, + ); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::set_team { + issuer, + admin, + freezer, + } => { + let output = >::set_team( + discriminant, + handle, + issuer, + admin, + freezer, + ); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::symbol {} => { + let output = >::symbol(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::thaw { account } => { + let output = >::thaw(discriminant, handle, account); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::thaw_asset {} => { + let output = >::thaw_asset(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::total_supply {} => { + let output = >::total_supply(discriminant, handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::transfer { to, value } => { + let output = >::transfer(discriminant, handle, to, value); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::transfer_from { from, to, value } => { + let output = + >::transfer_from(discriminant, handle, from, to, value); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::transfer_ownership { owner } => { + let output = + >::transfer_ownership(discriminant, handle, owner); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + }; + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output, + }) + } + pub fn supports_selector(selector: u32) -> bool { + match selector { + 117300739u32 => true, + 157198259u32 => true, + 404098525u32 => true, + 484305945u32 => true, + 599290589u32 => true, + 826074471u32 => true, + 910484757u32 => true, + 936559348u32 => true, + 1086394137u32 => true, + 1374431959u32 => true, + 1587675670u32 => true, + 1804030401u32 => true, + 1889567281u32 => true, + 2127478272u32 => true, + 2367676207u32 => true, + 2514000705u32 => true, + 2646777772u32 => true, + 2835717307u32 => true, + 3352902745u32 => true, + 3552201630u32 => true, + 3566436177u32 => true, + 3573918927u32 => true, + 3714247998u32 => true, + 3999121892u32 => true, + 4021736498u32 => true, + 4030008324u32 => true, + 4076725131u32 => true, + 4173303445u32 => true, + _ => false, + } + } + pub fn selectors() -> &'static [u32] { + &[ + 117300739u32, + 157198259u32, + 404098525u32, + 484305945u32, + 599290589u32, + 826074471u32, + 910484757u32, + 936559348u32, + 1086394137u32, + 1374431959u32, + 1587675670u32, + 1804030401u32, + 1889567281u32, + 2127478272u32, + 2367676207u32, + 2514000705u32, + 2646777772u32, + 2835717307u32, + 3352902745u32, + 3552201630u32, + 3566436177u32, + 3573918927u32, + 3714247998u32, + 3999121892u32, + 4021736498u32, + 4030008324u32, + 4076725131u32, + 4173303445u32, + ] + } + pub fn allowance_selectors() -> &'static [u32] { + &[3714247998u32] + } + pub fn approve_selectors() -> &'static [u32] { + &[157198259u32] + } + pub fn balance_of_selectors() -> &'static [u32] { + &[1889567281u32] + } + pub fn burn_selectors() -> &'static [u32] { + &[2646777772u32] + } + pub fn clear_metadata_selectors() -> &'static [u32] { + &[4021736498u32, 3552201630u32] + } + pub fn decimals_selectors() -> &'static [u32] { + &[826074471u32] + } + pub fn eip2612_domain_separator_selectors() -> &'static [u32] { + &[910484757u32] + } + pub fn eip2612_nonces_selectors() -> &'static [u32] { + &[2127478272u32] + } + pub fn eip2612_permit_selectors() -> &'static [u32] { + &[3573918927u32] + } + pub fn freeze_selectors() -> &'static [u32] { + &[2367676207u32] + } + pub fn freeze_asset_selectors() -> &'static [u32] { + &[3566436177u32, 1804030401u32] + } + pub fn mint_selectors() -> &'static [u32] { + &[1086394137u32] + } + pub fn name_selectors() -> &'static [u32] { + &[117300739u32] + } + pub fn set_metadata_selectors() -> &'static [u32] { + &[936559348u32, 3999121892u32] + } + pub fn set_team_selectors() -> &'static [u32] { + &[3352902745u32, 4173303445u32] + } + pub fn symbol_selectors() -> &'static [u32] { + &[2514000705u32] + } + pub fn thaw_selectors() -> &'static [u32] { + &[1587675670u32] + } + pub fn thaw_asset_selectors() -> &'static [u32] { + &[1374431959u32, 484305945u32] + } + pub fn total_supply_selectors() -> &'static [u32] { + &[404098525u32] + } + pub fn transfer_selectors() -> &'static [u32] { + &[2835717307u32] + } + pub fn transfer_from_selectors() -> &'static [u32] { + &[599290589u32] + } + pub fn transfer_ownership_selectors() -> &'static [u32] { + &[4076725131u32, 4030008324u32] + } + pub fn encode(self) -> ::sp_std::vec::Vec { + use precompile_utils::solidity::codec::Writer; + match self { + Self::allowance { owner, spender } => Writer::new_with_selector(3714247998u32) + .write(owner) + .write(spender) + .build(), + Self::approve { spender, value } => Writer::new_with_selector(157198259u32) + .write(spender) + .write(value) + .build(), + Self::balance_of { who } => Writer::new_with_selector(1889567281u32).write(who).build(), + Self::burn { from, value } => Writer::new_with_selector(2646777772u32) + .write(from) + .write(value) + .build(), + Self::clear_metadata {} => Writer::new_with_selector(4021736498u32).build(), + Self::decimals {} => Writer::new_with_selector(826074471u32).build(), + Self::eip2612_domain_separator {} => Writer::new_with_selector(910484757u32).build(), + Self::eip2612_nonces { owner } => Writer::new_with_selector(2127478272u32) + .write(owner) + .build(), + Self::eip2612_permit { + owner, + spender, + value, + deadline, + v, + r, + s, + } => Writer::new_with_selector(3573918927u32) + .write(owner) + .write(spender) + .write(value) + .write(deadline) + .write(v) + .write(r) + .write(s) + .build(), + Self::freeze { account } => Writer::new_with_selector(2367676207u32) + .write(account) + .build(), + Self::freeze_asset {} => Writer::new_with_selector(3566436177u32).build(), + Self::mint { to, value } => Writer::new_with_selector(1086394137u32) + .write(to) + .write(value) + .build(), + Self::name {} => Writer::new_with_selector(117300739u32).build(), + Self::set_metadata { + name, + symbol, + decimals, + } => Writer::new_with_selector(936559348u32) + .write(name) + .write(symbol) + .write(decimals) + .build(), + Self::set_team { + issuer, + admin, + freezer, + } => Writer::new_with_selector(3352902745u32) + .write(issuer) + .write(admin) + .write(freezer) + .build(), + Self::symbol {} => Writer::new_with_selector(2514000705u32).build(), + Self::thaw { account } => Writer::new_with_selector(1587675670u32) + .write(account) + .build(), + Self::thaw_asset {} => Writer::new_with_selector(1374431959u32).build(), + Self::total_supply {} => Writer::new_with_selector(404098525u32).build(), + Self::transfer { to, value } => Writer::new_with_selector(2835717307u32) + .write(to) + .write(value) + .build(), + Self::transfer_from { from, to, value } => Writer::new_with_selector(599290589u32) + .write(from) + .write(to) + .write(value) + .build(), + Self::transfer_ownership { owner } => Writer::new_with_selector(4076725131u32) + .write(owner) + .build(), + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + } + } +} +impl From> for ::sp_std::vec::Vec +where + Runtime: Get, +{ + fn from(a: PrecompileSetCall) -> ::sp_std::vec::Vec { + a.encode() + } +} +impl ::fp_evm::PrecompileSet for PrecompileSet +where + Runtime: Get, +{ + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option<::precompile_utils::EvmResult<::fp_evm::PrecompileOutput>> { + let discriminant = match >::discriminant(handle.code_address()) { + Some(d) => d, + None => return None, + }; + Some( + >::parse_call_data(handle) + .and_then(|call| call.execute(discriminant, handle)), + ) + } + fn is_precompile(&self, address: H160, gas: u64) -> ::fp_evm::IsPrecompileResult { + >::discriminant(address, gas).is_some() + } +} +#[allow(non_snake_case)] +pub(crate) fn __PrecompileSet_test_solidity_signatures_inner() +where + Runtime: Get, +{ + use precompile_utils::solidity::Codec; + match ( + &"(address,address)", + &<(Address, Address) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "allowance" ), ), ); + } + } + }; + match ( + &"(address,uint256)", + &<(Address, U256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "approve" ), ), ); + } + } + }; + match (&"(address)", &<(Address,) as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "balance_of" ), ), ); + } + } + }; + match ( + &"(address,uint256)", + &<(Address, U256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "burn" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "clear_metadata" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "decimals" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "eip2612_domain_separator" ), ), ); + } + } + }; + match (&"(address)", &<(Address,) as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "eip2612_nonces" ), ), ); + } + } + }; + match ( + &"(address,address,uint256,uint256,uint8,bytes32,bytes32)", + &<(Address, Address, U256, U256, u8, H256, H256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "eip2612_permit" ), ), ); + } + } + }; + match (&"(address)", &<(Address,) as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "freeze" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "freeze_asset" ), ), ); + } + } + }; + match ( + &"(address,uint256)", + &<(Address, U256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "mint" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "name" ), ), ); + } + } + }; + match ( + &"(string,string,uint8)", + &<( + BoundedString>, + BoundedString>, + u8, + ) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "set_metadata" ), ), ); + } + } + }; + match ( + &"(address,address,address)", + &<(Address, Address, Address) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "set_team" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "symbol" ), ), ); + } + } + }; + match (&"(address)", &<(Address,) as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "thaw" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "thaw_asset" ), ), ); + } + } + }; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "total_supply" ), ), ); + } + } + }; + match ( + &"(address,uint256)", + &<(Address, U256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "transfer" ), ), ); + } + } + }; + match ( + &"(address,address,uint256)", + &<(Address, Address, U256) as Codec>::signature(), + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "transfer_from" ), ), ); + } + } + }; + match (&"(address)", &<(Address,) as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "transfer_ownership" ), ), ); + } + } + }; +} diff --git a/precompiles/utils_v2/macro/tests/expand/precompileset.rs b/precompiles/utils_v2/macro/tests/expand/precompileset.rs new file mode 100644 index 0000000000..d9fa8e91b9 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/precompileset.rs @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use core::marker::PhantomData; +use precompile_utils::{prelude::*, testing::PrecompileTesterExt, EvmResult}; +use sp_core::H160; + +// Based on Erc20AssetsPrecompileSet with stripped code. + +struct PrecompileSet(PhantomData); + +type Discriminant = u32; +type GetAssetsStringLimit = R; +type MockRuntime = ConstU32<42>; + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +#[precompile::test_concrete_types(MockRuntime)] +impl PrecompileSet +where + Runtime: Get, +{ + /// PrecompileSet discrimiant. Allows to knows if the address maps to an asset id, + /// and if this is the case which one. + #[precompile::discriminant] + fn discriminant(address: H160) -> Option { + todo!("discriminant") + } + + #[precompile::public("totalSupply()")] + fn total_supply(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("total_supply") + } + + #[precompile::public("balanceOf(address)")] + fn balance_of( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + who: Address, + ) -> EvmResult { + todo!("balance_of") + } + + #[precompile::public("allowance(address,address)")] + fn allowance( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + spender: Address, + ) -> EvmResult { + todo!("allowance") + } + + #[precompile::public("approve(address,uint256)")] + fn approve( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + spender: Address, + value: U256, + ) -> EvmResult { + todo!("approve") + } + + fn approve_inner( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: H160, + spender: H160, + value: U256, + ) -> EvmResult { + todo!("approve_inner") + } + + #[precompile::public("transfer(address,uint256)")] + fn transfer( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + to: Address, + value: U256, + ) -> EvmResult { + todo!("transfer") + } + + #[precompile::public("transferFrom(address,address,uint256)")] + fn transfer_from( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + from: Address, + to: Address, + value: U256, + ) -> EvmResult { + todo!("transfer_from") + } + + #[precompile::public("name()")] + fn name( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + todo!("name") + } + + #[precompile::public("symbol()")] + fn symbol( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + todo!("symbol") + } + + #[precompile::public("decimals()")] + fn decimals(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("decimals") + } + + // From here: only for locals, we need to check whether we are in local assets otherwise fail + #[precompile::public("mint(address,uint256)")] + fn mint( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + to: Address, + value: U256, + ) -> EvmResult { + todo!("mint") + } + + #[precompile::public("burn(address,uint256)")] + fn burn( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + from: Address, + value: U256, + ) -> EvmResult { + todo!("burn") + } + + #[precompile::public("freeze(address)")] + fn freeze( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + account: Address, + ) -> EvmResult { + todo!("freeze") + } + + #[precompile::public("thaw(address)")] + fn thaw( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + account: Address, + ) -> EvmResult { + todo!("thaw") + } + + #[precompile::public("freezeAsset()")] + #[precompile::public("freeze_asset()")] + fn freeze_asset(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("freeze_asset") + } + + #[precompile::public("thawAsset()")] + #[precompile::public("thaw_asset()")] + fn thaw_asset(asset_id: Discriminant, handle: &mut impl PrecompileHandle) -> EvmResult { + todo!("thaw_asset") + } + + #[precompile::public("transferOwnership(address)")] + #[precompile::public("transfer_ownership(address)")] + fn transfer_ownership( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + ) -> EvmResult { + todo!("transfer_ownership") + } + + #[precompile::public("setTeam(address,address,address)")] + #[precompile::public("set_team(address,address,address)")] + fn set_team( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + issuer: Address, + admin: Address, + freezer: Address, + ) -> EvmResult { + todo!("set_team") + } + + #[precompile::public("setMetadata(string,string,uint8)")] + #[precompile::public("set_metadata(string,string,uint8)")] + fn set_metadata( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + name: BoundedString>, + symbol: BoundedString>, + decimals: u8, + ) -> EvmResult { + todo!("set_metadata") + } + + #[precompile::public("clearMetadata()")] + #[precompile::public("clear_metadata()")] + fn clear_metadata( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + todo!("clear_metadata") + } + + #[precompile::public("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")] + fn eip2612_permit( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + spender: Address, + value: U256, + deadline: U256, + v: u8, + r: H256, + s: H256, + ) -> EvmResult { + todo!("eip2612_permit") + } + + #[precompile::public("nonces(address)")] + #[precompile::view] + fn eip2612_nonces( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + owner: Address, + ) -> EvmResult { + todo!("eip2612_nonces") + } + + #[precompile::public("DOMAIN_SEPARATOR()")] + #[precompile::view] + fn eip2612_domain_separator( + asset_id: Discriminant, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + todo!("eip2612_domain_separator") + } +} diff --git a/precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs b/precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs new file mode 100644 index 0000000000..6d11310310 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs @@ -0,0 +1,108 @@ +use precompile_utils::{prelude::*, EvmResult}; +use sp_core::{H160, U256}; +struct ExamplePrecompile; +impl ExamplePrecompile { + fn example(handle: &mut impl PrecompileHandle) -> EvmResult<(Address, U256, UnboundedBytes)> { + ::core::panicking::panic_fmt(format_args!( + "not yet implemented: {0}", + format_args!("example") + )) + } +} +#[allow(non_camel_case_types)] +pub enum ExamplePrecompileCall { + example {}, + #[doc(hidden)] + __phantom(::core::marker::PhantomData<()>, ::core::convert::Infallible), +} +impl ExamplePrecompileCall { + pub fn parse_call_data( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult { + use precompile_utils::solidity::revert::RevertReason; + let input = handle.input(); + let selector = input.get(0..4).map(|s| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(s); + u32::from_be_bytes(buffer) + }); + match selector { + Some(1412775727u32) => Self::_parse_example(handle), + Some(_) => Err(RevertReason::UnknownSelector.into()), + None => Err(RevertReason::read_out_of_bounds("selector").into()), + } + } + fn _parse_example(handle: &mut impl PrecompileHandle) -> ::precompile_utils::EvmResult { + use precompile_utils::{ + evm::handle::PrecompileHandleExt, + solidity::{modifier::FunctionModifier, revert::InjectBacktrace}, + }; + handle.check_function_modifier(FunctionModifier::NonPayable)?; + Ok(Self::example {}) + } + pub fn execute( + self, + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + use fp_evm::{ExitSucceed, PrecompileOutput}; + use precompile_utils::solidity::codec::Writer; + let output = match self { + Self::example {} => { + let output = ::example(handle); + ::precompile_utils::solidity::encode_return_value(output?) + } + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + }; + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output, + }) + } + pub fn supports_selector(selector: u32) -> bool { + match selector { + 1412775727u32 => true, + _ => false, + } + } + pub fn selectors() -> &'static [u32] { + &[1412775727u32] + } + pub fn example_selectors() -> &'static [u32] { + &[1412775727u32] + } + pub fn encode(self) -> ::sp_std::vec::Vec { + use precompile_utils::solidity::codec::Writer; + match self { + Self::example {} => Writer::new_with_selector(1412775727u32).build(), + Self::__phantom(_, _) => { + ::core::panicking::panic_fmt(format_args!("__phantom variant should not be used")) + } + } + } +} +impl From for ::sp_std::vec::Vec { + fn from(a: ExamplePrecompileCall) -> ::sp_std::vec::Vec { + a.encode() + } +} +impl ::fp_evm::Precompile for ExamplePrecompile { + fn execute( + handle: &mut impl PrecompileHandle, + ) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> { + ::parse_call_data(handle)?.execute(handle) + } +} +#[allow(non_snake_case)] +pub(crate) fn __ExamplePrecompile_test_solidity_signatures_inner() { + use precompile_utils::solidity::Codec; + match (&"()", &<() as Codec>::signature()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::Some( format_args!( "{0} function signature doesn\'t match (left: attribute, right: computed from Rust types)", "example" ), ), ); + } + } + }; +} diff --git a/precompiles/utils_v2/macro/tests/expand/returns_tuple.rs b/precompiles/utils_v2/macro/tests/expand/returns_tuple.rs new file mode 100644 index 0000000000..8fa5b160db --- /dev/null +++ b/precompiles/utils_v2/macro/tests/expand/returns_tuple.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use precompile_utils::{prelude::*, EvmResult}; +use sp_core::{H160, U256}; + +struct ExamplePrecompile; + +#[precompile_utils_macro::precompile] +impl ExamplePrecompile { + #[precompile::public("example()")] + fn example(handle: &mut impl PrecompileHandle) -> EvmResult<(Address, U256, UnboundedBytes)> { + todo!("example") + } +} diff --git a/precompiles/utils_v2/macro/tests/pass/derive_codec.rs b/precompiles/utils_v2/macro/tests/pass/derive_codec.rs new file mode 100644 index 0000000000..8d63c2bdce --- /dev/null +++ b/precompiles/utils_v2/macro/tests/pass/derive_codec.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use precompile_utils::solidity::codec::{Address, Codec, Reader, Writer}; +use sp_core::H160; + +#[derive(Debug, Clone, PartialEq, Eq, Codec)] +struct StaticSize { + id: u32, + address: Address, +} + +#[derive(Debug, Clone, PartialEq, Eq, Codec)] +struct DynamicSize { + id: u32, + array: Vec, +} + +fn main() { + // static + let static_size = StaticSize { + id: 5, + address: H160::repeat_byte(0x42).into(), + }; + + assert!(StaticSize::has_static_size()); + assert_eq!(&StaticSize::signature(), "(uint32,address)"); + + let bytes = Writer::new().write(static_size.clone()).build(); + assert_eq!( + bytes, + Writer::new() + .write(5u32) + .write(Address::from(H160::repeat_byte(0x42))) + .build() + ); + + let mut reader = Reader::new(&bytes); + let static_size_2: StaticSize = reader.read().expect("to decode properly"); + assert_eq!(static_size_2, static_size); + + // dynamic + let dynamic_size = DynamicSize { + id: 6, + array: vec![10u32, 15u32], + }; + assert!(!DynamicSize::::has_static_size()); + assert_eq!(DynamicSize::::signature(), "(uint32,uint32[])"); + + let bytes = Writer::new().write(dynamic_size.clone()).build(); + assert_eq!( + bytes, + Writer::new() + .write(0x20u32) // offset of struct + .write(6u32) // id + .write(0x40u32) // array offset + .write(2u32) // array size + .write(10u32) // array[0] + .write(15u32) // array[1] + .build() + ); + + let mut reader = Reader::new(&bytes); + let dynamic_size_2: DynamicSize = reader.read().expect("to decode properly"); + assert_eq!(dynamic_size_2, dynamic_size); +} diff --git a/precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs b/precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs new file mode 100644 index 0000000000..d684bb821e --- /dev/null +++ b/precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs @@ -0,0 +1,75 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +use precompile_utils::{prelude::*, testing::PrecompileTesterExt, EvmResult}; +use sp_core::H160; + +pub struct PrecompileSet; + +#[precompile_utils_macro::precompile] +#[precompile::precompile_set] +impl PrecompileSet { + #[precompile::discriminant] + fn discriminant(_: H160) -> Option<()> { + Some(()) + } + + #[precompile::public("default()")] + fn default(_: (), _: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } + + #[precompile::public("view()")] + #[precompile::view] + fn view(_: (), _: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } + + #[precompile::public("payable()")] + #[precompile::payable] + fn payable(_: (), _: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } +} + +fn main() { + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::default {}) + .with_value(1) + .execute_reverts(|output| output == b"Function is not payable"); + + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::default {}) + .with_static_call(true) + .execute_reverts(|output| output == b"Can't call non-static function in static context"); + + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::view {}) + .with_value(1) + .execute_reverts(|output| output == b"Function is not payable"); + + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::view {}) + .with_static_call(true) + .execute_returns(()); + + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::payable {}) + .with_value(1) + .execute_returns(()); + + PrecompileSet + .prepare_test([0u8; 20], [0u8; 20], PrecompileSetCall::payable {}) + .with_static_call(true) + .execute_reverts(|output| output == b"Can't call non-static function in static context"); +} diff --git a/precompiles/utils_v2/macro/tests/tests.rs b/precompiles/utils_v2/macro/tests/tests.rs new file mode 100644 index 0000000000..dd456be727 --- /dev/null +++ b/precompiles/utils_v2/macro/tests/tests.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use sp_core_hashing::keccak_256; + +#[test] +fn test_keccak256() { + assert_eq!( + &precompile_utils_macro_v2::keccak256!(""), + keccak_256(b"").as_slice(), + ); + assert_eq!( + &precompile_utils_macro_v2::keccak256!("toto()"), + keccak_256(b"toto()").as_slice(), + ); + assert_ne!( + &precompile_utils_macro_v2::keccak256!("toto()"), + keccak_256(b"tata()").as_slice(), + ); +} + +#[test] +#[ignore] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile-fail/**/*.rs"); + t.pass("tests/pass/**/*.rs"); +} + +// Cargo expand is not supported on stable rust +#[test] +#[ignore] +fn expand() { + // Use `expand` to update the expansions + // Replace it with `expand_without_refresh` afterward so that + // CI checks the expension don't change + + // macrotest::expand("tests/expand/**/*.rs"); + macrotest::expand_without_refresh("tests/expand/**/*.rs"); +} diff --git a/precompiles/utils_v2/src/evm/costs.rs b/precompiles/utils_v2/src/evm/costs.rs new file mode 100644 index 0000000000..34fb2a4265 --- /dev/null +++ b/precompiles/utils_v2/src/evm/costs.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Cost calculations. +//! TODO: PR EVM to make those cost calculations public. + +use crate::EvmResult; +use fp_evm::{ExitError, PrecompileFailure}; +use sp_core::U256; + +pub fn log_costs(topics: usize, data_len: usize) -> EvmResult { + // Cost calculation is copied from EVM code that is not publicly exposed by the crates. + // https://github.com/rust-blockchain/evm/blob/master/gasometer/src/costs.rs#L148 + + const G_LOG: u64 = 375; + const G_LOGDATA: u64 = 8; + const G_LOGTOPIC: u64 = 375; + + let topic_cost = G_LOGTOPIC + .checked_mul(topics as u64) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + let data_cost = G_LOGDATA + .checked_mul(data_len as u64) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + G_LOG + .checked_add(topic_cost) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })? + .checked_add(data_cost) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) +} + +// Compute the cost of doing a subcall. +// Some parameters cannot be known in advance, so we estimate the worst possible cost. +pub fn call_cost(value: U256, config: &evm::Config) -> u64 { + // Copied from EVM code since not public. + pub const G_CALLVALUE: u64 = 9000; + pub const G_NEWACCOUNT: u64 = 25000; + + fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { + if config.increase_state_access_gas { + if is_cold { + config.gas_account_access_cold + } else { + config.gas_storage_read_warm + } + } else { + regular_value + } + } + + fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { + if is_call_or_callcode && transfers_value { + G_CALLVALUE + } else { + 0 + } + } + + fn new_cost( + is_call_or_staticcall: bool, + new_account: bool, + transfers_value: bool, + config: &evm::Config, + ) -> u64 { + let eip161 = !config.empty_considered_exists; + if is_call_or_staticcall { + if eip161 { + if transfers_value && new_account { + G_NEWACCOUNT + } else { + 0 + } + } else if new_account { + G_NEWACCOUNT + } else { + 0 + } + } else { + 0 + } + } + + let transfers_value = value != U256::default(); + let is_cold = true; + let is_call_or_callcode = true; + let is_call_or_staticcall = true; + let new_account = true; + + address_access_cost(is_cold, config.gas_call, config) + + xfer_cost(is_call_or_callcode, transfers_value) + + new_cost(is_call_or_staticcall, new_account, transfers_value, config) +} diff --git a/precompiles/utils_v2/src/evm/handle.rs b/precompiles/utils_v2/src/evm/handle.rs new file mode 100644 index 0000000000..9f68718cfd --- /dev/null +++ b/precompiles/utils_v2/src/evm/handle.rs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + solidity::{ + codec::Reader, + modifier::FunctionModifier, + revert::{MayRevert, RevertReason}, + }, + EvmResult, +}; +use fp_evm::{Log, PrecompileHandle}; + +pub trait PrecompileHandleExt: PrecompileHandle { + /// Record cost of one DB read manually. + /// The max encoded lenght of the data that will be read should be provided. + fn record_db_read( + &mut self, + data_max_encoded_len: usize, + ) -> Result<(), evm::ExitError>; + + /// Record cost of a log manually. + /// This can be useful to record log costs early when their content have static size. + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; + + /// Record cost of logs. + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult; + + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> MayRevert; + + /// Read the selector from the input data. + fn read_u32_selector(&self) -> MayRevert; + + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> MayRevert; +} + +impl PrecompileHandleExt for T { + fn record_db_read( + &mut self, + data_max_encoded_len: usize, + ) -> Result<(), evm::ExitError> { + self.record_cost(crate::prelude::RuntimeHelper::::db_read_gas_cost())?; + // TODO: record ref time when precompile will be benchmarked + self.record_external_cost(None, Some(data_max_encoded_len as u64)) + } + + /// Record cost of a log manualy. + /// This can be useful to record log costs early when their content have static size. + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { + self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?; + + Ok(()) + } + + /// Record cost of logs. + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult { + for log in logs { + self.record_log_costs_manual(log.topics.len(), log.data.len())?; + } + + Ok(()) + } + + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> MayRevert { + crate::solidity::modifier::check_function_modifier( + self.context(), + self.is_static(), + modifier, + ) + } + + /// Read the selector from the input data as u32. + fn read_u32_selector(&self) -> MayRevert { + crate::solidity::codec::selector(self.input()) + .ok_or(RevertReason::read_out_of_bounds("selector").into()) + } + + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> MayRevert { + Reader::new_skip_selector(self.input()) + } +} + +environmental::environmental!(EVM_CONTEXT: trait PrecompileHandle); + +pub fn using_precompile_handle<'a, R, F: FnOnce() -> R>( + precompile_handle: &'a mut dyn PrecompileHandle, + mutator: F, +) -> R { + // # Safety + // + // unsafe rust does not mean unsafe, but "the compiler cannot guarantee the safety of the + // memory". + // + // The only risk here is that the lifetime 'a comes to its end while the global variable + // `EVM_CONTEXT` still contains the reference to the precompile handle. + // The `using` method guarantee that it can't happen because the global variable is freed right + // after the execution of the `mutator` closure (whatever the result of the execution). + unsafe { + EVM_CONTEXT::using( + core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>( + precompile_handle, + ), + mutator, + ) + } +} + +pub fn with_precompile_handle R>(f: F) -> Option { + EVM_CONTEXT::with(|precompile_handle| f(precompile_handle)) +} + +#[cfg(test)] +mod tests { + use super::*; + + struct MockPrecompileHandle; + impl PrecompileHandle for MockPrecompileHandle { + fn call( + &mut self, + _: sp_core::H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &evm::Context, + ) -> (evm::ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn remaining_gas(&self) -> u64 { + unimplemented!() + } + + fn log( + &mut self, + _: sp_core::H160, + _: Vec, + _: Vec, + ) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn code_address(&self) -> sp_core::H160 { + unimplemented!() + } + + fn input(&self) -> &[u8] { + unimplemented!() + } + + fn context(&self) -> &evm::Context { + unimplemented!() + } + + fn is_static(&self) -> bool { + true + } + + fn gas_limit(&self) -> Option { + unimplemented!() + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + ) -> Result<(), fp_evm::ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} + } + + #[test] + fn with_precompile_handle_without_context() { + assert_eq!(with_precompile_handle(|_| {}), None); + } + + #[test] + fn with_precompile_handle_with_context() { + let mut precompile_handle = MockPrecompileHandle; + + assert_eq!( + using_precompile_handle(&mut precompile_handle, || with_precompile_handle( + |handle| handle.is_static() + )), + Some(true) + ); + } +} diff --git a/precompiles/utils_v2/src/evm/logs.rs b/precompiles/utils_v2/src/evm/logs.rs new file mode 100644 index 0000000000..0ce6367f40 --- /dev/null +++ b/precompiles/utils_v2/src/evm/logs.rs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::EvmResult; +use pallet_evm::{Log, PrecompileHandle}; +use sp_core::{H160, H256}; +use sp_std::{vec, vec::Vec}; + +/// Create a 0-topic log. +#[must_use] +pub fn log0(address: impl Into, data: impl Into>) -> Log { + Log { + address: address.into(), + topics: vec![], + data: data.into(), + } +} + +/// Create a 1-topic log. +#[must_use] +pub fn log1(address: impl Into, topic0: impl Into, data: impl Into>) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into()], + data: data.into(), + } +} + +/// Create a 2-topics log. +#[must_use] +pub fn log2( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into()], + data: data.into(), + } +} + +/// Create a 3-topics log. +#[must_use] +pub fn log3( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + topic2: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into(), topic2.into()], + data: data.into(), + } +} + +/// Create a 4-topics log. +#[must_use] +pub fn log4( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + topic2: impl Into, + topic3: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into(), topic2.into(), topic3.into()], + data: data.into(), + } +} + +/// Extension trait allowing to record logs into a PrecompileHandle. +pub trait LogExt { + fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult; + + fn compute_cost(&self) -> EvmResult; +} + +impl LogExt for Log { + fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult { + handle.log(self.address, self.topics, self.data)?; + Ok(()) + } + + fn compute_cost(&self) -> EvmResult { + crate::evm::costs::log_costs(self.topics.len(), self.data.len()) + } +} diff --git a/precompiles/utils_v2/src/evm/mod.rs b/precompiles/utils_v2/src/evm/mod.rs new file mode 100644 index 0000000000..ffd1ef42e0 --- /dev/null +++ b/precompiles/utils_v2/src/evm/mod.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod costs; +pub mod handle; +pub mod logs; diff --git a/precompiles/utils_v2/src/lib.rs b/precompiles/utils_v2/src/lib.rs new file mode 100644 index 0000000000..10fd3cad5f --- /dev/null +++ b/precompiles/utils_v2/src/lib.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +// Allows to use inside this crate `solidity::Codec` derive macro,which depends on +// `precompile_utils` being in the list of imported crates. +extern crate self as precompile_utils; + +pub mod evm; +pub mod precompile_set; +pub mod substrate; + +pub mod solidity; + +#[cfg(feature = "testing")] +pub mod testing; + +use fp_evm::PrecompileFailure; + +// pub mod data; + +// pub use data::{solidity::Codec, Reader, Writer}; +pub use fp_evm::Precompile; +pub use precompile_utils_macro_v2::{keccak256, precompile, precompile_name_from_address}; + +/// Alias for Result returning an EVM precompile error. +pub type EvmResult = Result; + +pub mod prelude { + pub use { + crate::{ + evm::{ + handle::PrecompileHandleExt, + logs::{log0, log1, log2, log3, log4, LogExt}, + }, + precompile_set::DiscriminantResult, + solidity::{ + // We export solidity itself to encourage using `solidity::Codec` to avoid confusion + // with parity_scale_codec, + self, + codec::{ + Address, + BoundedBytes, + BoundedString, + BoundedVec, + // Allow usage of Codec methods while not exporting the name directly. + Codec as _, + Convert, + UnboundedBytes, + UnboundedString, + }, + revert::{ + revert, BacktraceExt, InjectBacktrace, MayRevert, Revert, RevertExt, + RevertReason, + }, + }, + substrate::{RuntimeHelper, TryDispatchError}, + EvmResult, + }, + alloc::string::String, + pallet_evm::{PrecompileHandle, PrecompileOutput}, + precompile_utils_macro_v2::{keccak256, precompile}, + }; +} diff --git a/precompiles/utils_v2/src/precompile_set.rs b/precompiles/utils_v2/src/precompile_set.rs new file mode 100644 index 0000000000..8625906c8f --- /dev/null +++ b/precompiles/utils_v2/src/precompile_set.rs @@ -0,0 +1,1100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Provide utils to assemble precompiles and precompilesets into a +//! final precompile set with security checks. All security checks are enabled by +//! default and must be disabled explicely throught type annotations. + +use crate::{ + evm::handle::PrecompileHandleExt, + solidity::{codec::String, revert::revert}, + EvmResult, +}; +use fp_evm::{ + ExitError, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileResult, PrecompileSet, +}; +use frame_support::pallet_prelude::Get; +use impl_trait_for_tuples::impl_for_tuples; +use pallet_evm::AddressMapping; +use sp_core::{H160, H256}; +use sp_std::{ + cell::RefCell, collections::btree_map::BTreeMap, marker::PhantomData, ops::RangeInclusive, vec, + vec::Vec, +}; + +/// Trait representing checks that can be made on a precompile call. +/// Types implementing this trait are made to be chained in a tuple. +/// +/// For that reason every method returns an Option, None meaning that +/// the implementor have no constraint and the decision is left to +/// latter elements in the chain. If None is returned by all elements of +/// the chain then sensible defaults are used. +/// +/// Both `PrecompileAt` and `PrecompileSetStartingWith` have a type parameter that must +/// implement this trait to configure the checks of the precompile(set) it represents. +pub trait PrecompileChecks { + #[inline(always)] + /// Is there a limit to the amount of recursions this precompile + /// can make using subcalls? 0 means this specific precompile will not + /// be callable as a subcall of itself, 1 will allow one level of recursion, + /// etc... + /// + /// If all checks return None, defaults to `Some(0)` (no recursion allowed). + fn recursion_limit() -> Option> { + None + } + + #[inline(always)] + /// Does this precompile supports being called with DELEGATECALL or CALLCODE? + /// + /// If all checks return None, defaults to `false`. + fn accept_delegate_call() -> Option { + None + } + + #[inline(always)] + /// Is this precompile callable by a smart contract? + /// + /// If all checks return None, defaults to `false`. + fn callable_by_smart_contract(_caller: H160, _called_selector: Option) -> Option { + None + } + + #[inline(always)] + /// Is this precompile callable by a precompile? + /// + /// If all checks return None, defaults to `false`. + fn callable_by_precompile(_caller: H160, _called_selector: Option) -> Option { + None + } + + #[inline(always)] + /// Is this precompile able to do subcalls? + /// + /// If all checks return None, defaults to `false`. + fn allow_subcalls() -> Option { + None + } + + /// Summarize the checks when being called by a smart contract. + fn callable_by_smart_contract_summary() -> Option { + None + } + + /// Summarize the checks when being called by a precompile. + fn callable_by_precompile_summary() -> Option { + None + } +} + +#[derive(Debug, Clone)] +pub enum DiscriminantResult { + Some(T, u64), + None(u64), + OutOfGas, +} + +impl From> for IsPrecompileResult { + fn from(val: DiscriminantResult) -> Self { + match val { + DiscriminantResult::::Some(_, extra_cost) => IsPrecompileResult::Answer { + is_precompile: true, + extra_cost, + }, + DiscriminantResult::::None(extra_cost) => IsPrecompileResult::Answer { + is_precompile: false, + extra_cost, + }, + DiscriminantResult::::OutOfGas => IsPrecompileResult::OutOfGas, + } + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] +pub enum PrecompileKind { + Single(H160), + Prefixed(Vec), +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] +pub struct PrecompileCheckSummary { + pub name: Option, + pub precompile_kind: PrecompileKind, + pub recursion_limit: Option, + pub accept_delegate_call: bool, + pub callable_by_smart_contract: String, + pub callable_by_precompile: String, +} + +#[impl_for_tuples(0, 20)] +impl PrecompileChecks for Tuple { + #[inline(always)] + fn recursion_limit() -> Option> { + for_tuples!(#( + if let Some(check) = Tuple::recursion_limit() { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn accept_delegate_call() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::accept_delegate_call() { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_smart_contract(caller, called_selector) { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_precompile(caller, called_selector) { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn allow_subcalls() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::allow_subcalls() { + return Some(check); + } + )*); + + None + } + + fn callable_by_smart_contract_summary() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_smart_contract_summary() { + return Some(check); + } + )*); + + None + } + + fn callable_by_precompile_summary() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_precompile_summary() { + return Some(check); + } + )*); + + None + } +} + +/// Precompile can be called using DELEGATECALL/CALLCODE. +pub struct AcceptDelegateCall; + +impl PrecompileChecks for AcceptDelegateCall { + #[inline(always)] + fn accept_delegate_call() -> Option { + Some(true) + } +} + +/// Precompile is able to do subcalls with provided nesting limit. +pub struct SubcallWithMaxNesting; + +impl PrecompileChecks for SubcallWithMaxNesting { + #[inline(always)] + fn recursion_limit() -> Option> { + Some(Some(R)) + } + + #[inline(always)] + fn allow_subcalls() -> Option { + Some(true) + } +} + +pub trait SelectorFilter { + fn is_allowed(_caller: H160, _selector: Option) -> bool; + + fn description() -> String; +} +pub struct ForAllSelectors; +impl SelectorFilter for ForAllSelectors { + fn is_allowed(_caller: H160, _selector: Option) -> bool { + true + } + + fn description() -> String { + "Allowed for all selectors and callers".into() + } +} + +pub struct OnlyFrom(PhantomData); +impl> SelectorFilter for OnlyFrom { + fn is_allowed(caller: H160, _selector: Option) -> bool { + caller == T::get() + } + + fn description() -> String { + alloc::format!("Allowed for all selectors only if called from {}", T::get()) + } +} + +pub struct CallableByContract(PhantomData); + +impl PrecompileChecks for CallableByContract { + #[inline(always)] + fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { + Some(T::is_allowed(caller, called_selector)) + } + + fn callable_by_smart_contract_summary() -> Option { + Some(T::description()) + } +} + +/// Precompiles are allowed to call this precompile. +pub struct CallableByPrecompile(PhantomData); + +impl PrecompileChecks for CallableByPrecompile { + #[inline(always)] + fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { + Some(T::is_allowed(caller, called_selector)) + } + + fn callable_by_precompile_summary() -> Option { + Some(T::description()) + } +} + +/// The type of EVM address. +#[derive(PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum AddressType { + /// The code stored at the address is less than 5 bytes, but not well known. + Unknown, + /// No code is stored at the address, therefore is EOA. + EOA, + /// The 5-byte magic constant for a precompile is stored at the address. + Precompile, + /// The code is greater than 5-bytes, potentially a Smart Contract. + Contract, +} + +/// Retrieves the type of address demarcated by `AddressType`. +pub fn get_address_type( + handle: &mut impl PrecompileHandle, + address: H160, +) -> Result { + // AccountCodesMetadata: + // Blake2128(16) + H160(20) + CodeMetadata(40) + handle.record_db_read::(76)?; + let code_len = pallet_evm::Pallet::::account_code_metadata(address).size; + + // 0 => either EOA or precompile without dummy code + if code_len == 0 { + return Ok(AddressType::EOA); + } + + // dummy code is 5 bytes long, so any other len means it is a contract. + if code_len != 5 { + return Ok(AddressType::Contract); + } + + // check code matches dummy code + handle.record_db_read::(code_len as usize)?; + let code = pallet_evm::AccountCodes::::get(address); + if code == [0x60, 0x00, 0x60, 0x00, 0xfd] { + return Ok(AddressType::Precompile); + } + + Ok(AddressType::Unknown) +} + +fn is_address_eoa_or_precompile( + handle: &mut impl PrecompileHandle, + address: H160, +) -> Result { + match get_address_type::(handle, address)? { + AddressType::EOA | AddressType::Precompile => Ok(true), + _ => Ok(false), + } +} + +/// Common checks for precompile and precompile sets. +/// Don't contain recursion check as precompile sets have recursion check for each member. +fn common_checks( + handle: &mut impl PrecompileHandle, +) -> EvmResult<()> { + let code_address = handle.code_address(); + let caller = handle.context().caller; + + // Check DELEGATECALL config. + let accept_delegate_call = C::accept_delegate_call().unwrap_or(false); + if !accept_delegate_call && code_address != handle.context().address { + return Err(revert("Cannot be called with DELEGATECALL or CALLCODE")); + } + + // Extract which selector is called. + let selector = handle.input().get(0..4).map(|bytes| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(bytes); + u32::from_be_bytes(buffer) + }); + + // Is this selector callable from a smart contract? + let callable_by_smart_contract = + C::callable_by_smart_contract(caller, selector).unwrap_or(false); + if !callable_by_smart_contract && !is_address_eoa_or_precompile::(handle, caller)? { + return Err(revert("Function not callable by smart contracts")); + } + + // Is this selector callable from a precompile? + let callable_by_precompile = C::callable_by_precompile(caller, selector).unwrap_or(false); + if !callable_by_precompile && is_precompile_or_fail::(caller, handle.remaining_gas())? { + return Err(revert("Function not callable by precompiles")); + } + + Ok(()) +} + +pub fn is_precompile_or_fail(address: H160, gas: u64) -> EvmResult { + match ::PrecompilesValue::get().is_precompile(address, gas) { + IsPrecompileResult::Answer { is_precompile, .. } => Ok(is_precompile), + IsPrecompileResult::OutOfGas => Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }), + } +} + +pub struct AddressU64; +impl Get for AddressU64 { + #[inline(always)] + fn get() -> H160 { + H160::from_low_u64_be(N) + } +} + +pub struct RestrictiveHandle<'a, H> { + handle: &'a mut H, + allow_subcalls: bool, +} + +impl<'a, H: PrecompileHandle> PrecompileHandle for RestrictiveHandle<'a, H> { + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &evm::Context, + ) -> (evm::ExitReason, Vec) { + if !self.allow_subcalls { + return ( + evm::ExitReason::Revert(evm::ExitRevert::Reverted), + crate::solidity::revert::revert_as_bytes("subcalls disabled for this precompile"), + ); + } + + self.handle + .call(address, transfer, input, target_gas, is_static, context) + } + + fn record_cost(&mut self, cost: u64) -> Result<(), evm::ExitError> { + self.handle.record_cost(cost) + } + + fn remaining_gas(&self) -> u64 { + self.handle.remaining_gas() + } + + fn log( + &mut self, + address: H160, + topics: Vec, + data: Vec, + ) -> Result<(), evm::ExitError> { + self.handle.log(address, topics, data) + } + + fn code_address(&self) -> H160 { + self.handle.code_address() + } + + fn input(&self) -> &[u8] { + self.handle.input() + } + + fn context(&self) -> &evm::Context { + self.handle.context() + } + + fn is_static(&self) -> bool { + self.handle.is_static() + } + + fn gas_limit(&self) -> Option { + self.handle.gas_limit() + } + + fn record_external_cost( + &mut self, + ref_time: Option, + proof_size: Option, + ) -> Result<(), ExitError> { + self.handle.record_external_cost(ref_time, proof_size) + } + + fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { + self.handle.refund_external_cost(ref_time, proof_size) + } +} + +/// Allows to know if a precompile is active or not. +/// This allows to detect deactivated precompile, that are still considered precompiles by +/// the EVM but that will always revert when called. +pub trait IsActivePrecompile { + /// Is the provided address an active precompile, a precompile that has + /// not be deactivated. Note that a deactivated precompile is still considered a precompile + /// for the EVM, but it will always revert when called. + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; +} + +// INDIVIDUAL PRECOMPILE(SET) + +/// A fragment of a PrecompileSet. Should be implemented as is it +/// was a PrecompileSet containing only the precompile(set) it wraps. +/// They can be combined into a real PrecompileSet using `PrecompileSetBuilder`. +pub trait PrecompileSetFragment { + /// Instanciate the fragment. + fn new() -> Self; + + /// Execute the fragment. + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option; + + /// Is the provided address a precompile in this fragment? + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; + + /// Return the list of addresses covered by this fragment. + fn used_addresses(&self) -> Vec; + + /// Summarize + fn summarize_checks(&self) -> Vec; +} + +/// Wraps a stateless precompile: a type implementing the `Precompile` trait. +/// Type parameters allow to define: +/// - A: The address of the precompile +/// - R: The recursion limit (defaults to 1) +/// - D: If DELEGATECALL is supported (default to no) +pub struct PrecompileAt { + current_recursion_level: RefCell, + _phantom: PhantomData<(A, P, C)>, +} + +impl PrecompileSetFragment for PrecompileAt +where + A: Get, + P: Precompile, + C: PrecompileChecks, +{ + #[inline(always)] + fn new() -> Self { + Self { + current_recursion_level: RefCell::new(0), + _phantom: PhantomData, + } + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + let code_address = handle.code_address(); + + // Check if this is the address of the precompile. + if A::get() != code_address { + return None; + } + + // Perform common checks. + if let Err(err) = common_checks::(handle) { + return Some(Err(err)); + } + + // Check and increase recursion level if needed. + let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); + if let Some(max_recursion_level) = recursion_limit { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level) => { + if *recursion_level > max_recursion_level { + return Some(Err(revert("Precompile is called with too high nesting"))); + } + + *recursion_level += 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + // Subcall protection. + let allow_subcalls = C::allow_subcalls().unwrap_or(false); + let mut handle = RestrictiveHandle { + handle, + allow_subcalls, + }; + + let res = P::execute(&mut handle); + + // Decrease recursion level if needed. + if recursion_limit.is_some() { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level) => { + *recursion_level -= 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + Some(res) + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: C::recursion_limit().unwrap_or(Some(0)), + accept_delegate_call: C::accept_delegate_call().unwrap_or(false), + callable_by_smart_contract: C::callable_by_smart_contract_summary() + .unwrap_or_else(|| "Not callable".into()), + callable_by_precompile: C::callable_by_precompile_summary() + .unwrap_or_else(|| "Not callable".into()), + }] + } +} + +impl IsActivePrecompile for PrecompileAt +where + A: Get, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } +} + +/// Wraps an inner PrecompileSet with all its addresses starting with +/// a common prefix. +/// Type parameters allow to define: +/// - A: The common prefix +/// - D: If DELEGATECALL is supported (default to no) +pub struct PrecompileSetStartingWith { + precompile_set: P, + current_recursion_level: RefCell>, + _phantom: PhantomData<(A, C)>, +} + +impl PrecompileSetFragment for PrecompileSetStartingWith +where + A: Get<&'static [u8]>, + P: PrecompileSet + Default, + C: PrecompileChecks, +{ + #[inline(always)] + fn new() -> Self { + Self { + precompile_set: P::default(), + current_recursion_level: RefCell::new(BTreeMap::new()), + _phantom: PhantomData, + } + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + let code_address = handle.code_address(); + if !is_precompile_or_fail::(code_address, handle.remaining_gas()).ok()? { + return None; + } + // Perform common checks. + if let Err(err) = common_checks::(handle) { + return Some(Err(err)); + } + + // Check and increase recursion level if needed. + let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); + if let Some(max_recursion_level) = recursion_limit { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level_map) => { + let recursion_level = recursion_level_map.entry(code_address).or_insert(0); + + if *recursion_level > max_recursion_level { + return Some(Err(revert("Precompile is called with too high nesting"))); + } + + *recursion_level += 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + // Subcall protection. + let allow_subcalls = C::allow_subcalls().unwrap_or(false); + let mut handle = RestrictiveHandle { + handle, + allow_subcalls, + }; + + let res = self.precompile_set.execute(&mut handle); + + // Decrease recursion level if needed. + if recursion_limit.is_some() { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level_map) => { + let recursion_level = match recursion_level_map.get_mut(&code_address) { + Some(recursion_level) => recursion_level, + None => return Some(Err(revert("Couldn't retreive precompile nesting"))), + }; + + *recursion_level -= 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + res + } + + #[inline(always)] + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if address.as_bytes().starts_with(A::get()) { + return self.precompile_set.is_precompile(address, gas); + } + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + // TODO: We currently can't get the list of used addresses. + vec![] + } + + fn summarize_checks(&self) -> Vec { + let prefix = A::get(); + + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Prefixed(prefix.to_vec()), + recursion_limit: C::recursion_limit().unwrap_or(Some(0)), + accept_delegate_call: C::accept_delegate_call().unwrap_or(false), + callable_by_smart_contract: C::callable_by_smart_contract_summary() + .unwrap_or_else(|| "Not callable".into()), + callable_by_precompile: C::callable_by_precompile_summary() + .unwrap_or_else(|| "Not callable".into()), + }] + } +} + +impl IsActivePrecompile for PrecompileSetStartingWith +where + Self: PrecompileSetFragment, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.is_precompile(address, gas) + } +} + +/// Make a precompile that always revert. +/// Can be useful when writing tests. +pub struct RevertPrecompile(PhantomData); + +impl PrecompileSetFragment for RevertPrecompile +where + A: Get, +{ + #[inline(always)] + fn new() -> Self { + Self(PhantomData) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if A::get() == handle.code_address() { + Some(Err(revert("revert"))) + } else { + None + } + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: Some(0), + accept_delegate_call: true, + callable_by_smart_contract: "Reverts in all cases".into(), + callable_by_precompile: "Reverts in all cases".into(), + }] + } +} + +impl IsActivePrecompile for RevertPrecompile { + #[inline(always)] + fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + } + } +} + +/// A precompile that was removed from a precompile set. +/// Still considered a precompile but is inactive and always revert. +pub struct RemovedPrecompileAt(PhantomData); +impl PrecompileSetFragment for RemovedPrecompileAt +where + A: Get, +{ + #[inline(always)] + fn new() -> Self { + Self(PhantomData) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if A::get() == handle.code_address() { + Some(Err(revert("Removed precompile"))) + } else { + None + } + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: Some(0), + accept_delegate_call: true, + callable_by_smart_contract: "Reverts in all cases".into(), + callable_by_precompile: "Reverts in all cases".into(), + }] + } +} + +impl IsActivePrecompile for RemovedPrecompileAt { + #[inline(always)] + fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } +} + +// COMPOSITION OF PARTS +#[impl_for_tuples(1, 100)] +impl PrecompileSetFragment for Tuple { + #[inline(always)] + fn new() -> Self { + (for_tuples!(#( + Tuple::new() + ),*)) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + for_tuples!(#( + if let Some(res) = self.Tuple.execute::(handle) { + return Some(res); + } + )*); + + None + } + + #[inline(always)] + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + for_tuples!(#( + if let IsPrecompileResult::Answer { + is_precompile: true, + .. + } = self.Tuple.is_precompile(address, gas) { return IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + } + }; + )*); + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + let mut used_addresses = vec![]; + + for_tuples!(#( + let mut inner = self.Tuple.used_addresses(); + used_addresses.append(&mut inner); + )*); + + used_addresses + } + + fn summarize_checks(&self) -> Vec { + let mut checks = Vec::new(); + + for_tuples!(#( + let mut inner = self.Tuple.summarize_checks(); + checks.append(&mut inner); + )*); + + checks + } +} + +#[impl_for_tuples(1, 100)] +impl IsActivePrecompile for Tuple { + #[inline(always)] + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + for_tuples!(#( + if let IsPrecompileResult::Answer { + is_precompile: true, + .. + } = self.Tuple.is_active_precompile(address, gas) { return IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + } }; + )*); + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } +} + +/// Wraps a precompileset fragment into a range, and will skip processing it if the address +/// is out of the range. +pub struct PrecompilesInRangeInclusive { + inner: P, + range: RangeInclusive, + _phantom: PhantomData, +} + +impl PrecompileSetFragment for PrecompilesInRangeInclusive<(S, E), P> +where + S: Get, + E: Get, + P: PrecompileSetFragment, +{ + fn new() -> Self { + Self { + inner: P::new(), + range: RangeInclusive::new(S::get(), E::get()), + _phantom: PhantomData, + } + } + + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if self.range.contains(&handle.code_address()) { + self.inner.execute::(handle) + } else { + None + } + } + + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if self.range.contains(&address) { + self.inner.is_precompile(address, gas) + } else { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + } + + fn used_addresses(&self) -> Vec { + self.inner.used_addresses() + } + + fn summarize_checks(&self) -> Vec { + self.inner.summarize_checks() + } +} + +impl IsActivePrecompile for PrecompilesInRangeInclusive<(S, E), P> +where + P: IsActivePrecompile, +{ + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if self.range.contains(&address) { + self.inner.is_active_precompile(address, gas) + } else { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + } +} + +/// Wraps a tuple of `PrecompileSetFragment` to make a real `PrecompileSet`. +pub struct PrecompileSetBuilder { + inner: P, + _phantom: PhantomData, +} + +impl PrecompileSet for PrecompileSetBuilder { + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + self.inner.execute::(handle) + } + + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.inner.is_precompile(address, gas) + } +} + +impl IsActivePrecompile for PrecompileSetBuilder { + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.inner.is_active_precompile(address, gas) + } +} + +impl Default for PrecompileSetBuilder { + fn default() -> Self { + Self::new() + } +} + +impl PrecompileSetBuilder { + /// Create a new instance of the PrecompileSet. + pub fn new() -> Self { + Self { + inner: P::new(), + _phantom: PhantomData, + } + } + + /// Return the list of addresses contained in this PrecompileSet. + pub fn used_addresses() -> impl Iterator { + Self::new() + .inner + .used_addresses() + .into_iter() + .map(R::AddressMapping::into_account_id) + } + + pub fn summarize_checks(&self) -> Vec { + self.inner.summarize_checks() + } +} diff --git a/precompiles/utils_v2/src/solidity/codec/bytes.rs b/precompiles/utils_v2/src/solidity/codec/bytes.rs new file mode 100644 index 0000000000..6c33458732 --- /dev/null +++ b/precompiles/utils_v2/src/solidity/codec/bytes.rs @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use alloc::borrow::ToOwned; +use sp_core::{ConstU32, Get}; + +type ConstU32Max = ConstU32<{ u32::MAX }>; + +pub type UnboundedBytes = BoundedBytesString; +pub type BoundedBytes = BoundedBytesString; + +pub type UnboundedString = BoundedBytesString; +pub type BoundedString = BoundedBytesString; + +trait Kind { + fn signature() -> String; +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BytesKind; + +impl Kind for BytesKind { + fn signature() -> String { + String::from("bytes") + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StringKind; + +impl Kind for StringKind { + fn signature() -> String { + String::from("string") + } +} + +/// The `bytes/string` type of Solidity. +/// It is different from `Vec` which will be serialized with padding for each `u8` element +/// of the array, while `Bytes` is tightly packed. +#[derive(Debug)] +pub struct BoundedBytesString { + data: Vec, + _phantom: PhantomData<(K, S)>, +} + +impl> Clone for BoundedBytesString { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _phantom: PhantomData, + } + } +} + +impl PartialEq> for BoundedBytesString { + fn eq(&self, other: &BoundedBytesString) -> bool { + self.data.eq(&other.data) + } +} + +impl Eq for BoundedBytesString {} + +impl> BoundedBytesString { + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> { + sp_std::str::from_utf8(&self.data) + } +} + +impl> Codec for BoundedBytesString { + fn read(reader: &mut Reader) -> MayRevert { + let mut inner_reader = reader.read_pointer()?; + + // Read bytes/string size. + let array_size: usize = inner_reader + .read::() + .map_err(|_| RevertReason::read_out_of_bounds("length"))? + .try_into() + .map_err(|_| RevertReason::value_is_too_large("length"))?; + + if array_size > S::get() as usize { + return Err(RevertReason::value_is_too_large("length").into()); + } + + // Get valid range over the bytes data. + let range = inner_reader.move_cursor(array_size)?; + + let data = inner_reader + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds(K::signature()))?; + + let bytes = Self { + data: data.to_owned(), + _phantom: PhantomData, + }; + + Ok(bytes) + } + + fn write(writer: &mut Writer, value: Self) { + let value: Vec<_> = value.into(); + let length = value.len(); + + // Pad the data. + // Leave it as is if a multiple of 32, otherwise pad to next + // multiple or 32. + let chunks = length / 32; + let padded_size = match length % 32 { + 0 => chunks * 32, + _ => (chunks + 1) * 32, + }; + + let mut value = value.to_vec(); + value.resize(padded_size, 0); + + writer.write_pointer( + Writer::new() + .write(U256::from(length)) + .write_raw_bytes(&value) + .build(), + ); + } + + fn has_static_size() -> bool { + false + } + + fn signature() -> String { + K::signature() + } +} + +// BytesString <=> Vec/&[u8] + +impl From> for Vec { + fn from(value: BoundedBytesString) -> Self { + value.data + } +} + +impl From> for BoundedBytesString { + fn from(value: Vec) -> Self { + Self { + data: value, + _phantom: PhantomData, + } + } +} + +impl From<&[u8]> for BoundedBytesString { + fn from(value: &[u8]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[u8; N]> for BoundedBytesString { + fn from(value: [u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<&[u8; N]> for BoundedBytesString { + fn from(value: &[u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +// BytesString <=> String/str + +impl TryFrom> for String { + type Error = alloc::string::FromUtf8Error; + + fn try_from(value: BoundedBytesString) -> Result { + alloc::string::String::from_utf8(value.data) + } +} + +impl From<&str> for BoundedBytesString { + fn from(value: &str) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} + +impl From for BoundedBytesString { + fn from(value: String) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} diff --git a/precompiles/utils_v2/src/solidity/codec/mod.rs b/precompiles/utils_v2/src/solidity/codec/mod.rs new file mode 100644 index 0000000000..223213799b --- /dev/null +++ b/precompiles/utils_v2/src/solidity/codec/mod.rs @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Solidity encoding following the +//! [Contract ABI Specification](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#abi) + +pub mod bytes; +pub mod native; + +use crate::solidity::revert::{MayRevert, RevertReason}; +use core::{marker::PhantomData, ops::Range}; +use sp_core::{H256, U256}; +use sp_std::{convert::TryInto, vec, vec::Vec}; + +pub use alloc::string::String; +pub use bytes::{BoundedBytes, BoundedString, UnboundedBytes, UnboundedString}; +pub use native::{Address, BoundedVec}; + +// derive macro +pub use precompile_utils_macro_v2::Codec; + +/// Data that can be encoded/encoded followiong the Solidity ABI Specification. +pub trait Codec: Sized { + fn read(reader: &mut Reader) -> MayRevert; + fn write(writer: &mut Writer, value: Self); + fn has_static_size() -> bool; + fn signature() -> String; + fn is_explicit_tuple() -> bool { + false + } +} + +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple it is encoded as a Solidity tuple with dynamic-size offset. +fn encode(value: T) -> Vec { + Writer::new().write(value).build() +} + +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple every element is encoded without a prefixed offset. +/// It matches the encoding of Solidity function arguments and return value, or event data. +pub fn encode_arguments(value: T) -> Vec { + let output = encode(value); + if T::is_explicit_tuple() && !T::has_static_size() { + output[32..].to_vec() + } else { + output + } +} + +pub use self::{encode_arguments as encode_return_value, encode_arguments as encode_event_data}; + +/// Encode the value as the arguments of a Solidity function with given selector. +/// If `T` is a tuple each member represents an argument of the function. +pub fn encode_with_selector(selector: u32, value: T) -> Vec { + Writer::new_with_selector(selector) + .write_raw_bytes(&encode_arguments(value)) + .build() +} + +/// Decode the value from its Solidity ABI format. +/// If `T` is a tuple it is decoded as a Solidity tuple with dynamic-size offset. +fn decode(input: &[u8]) -> MayRevert { + Reader::new(input).read() +} + +/// Decode the value from its Solidity ABI format. +/// If `T` is a tuple every element is decoded without a prefixed offset. +/// It matches the encoding of Solidity function arguments and return value, or event data. +pub fn decode_arguments(input: &[u8]) -> MayRevert { + if T::is_explicit_tuple() && !T::has_static_size() { + let writer = Writer::new(); + let mut writer = writer.write(U256::from(32)); + writer.write_pointer(input.to_vec()); + let input = writer.build(); + decode(&input) + } else { + decode(input) + } +} + +pub use self::{decode_arguments as decode_return_value, decode_arguments as decode_event_data}; + +/// Extracts the selector from the start of the input, or returns `None` if the input is too short. +pub fn selector(input: &[u8]) -> Option { + input.get(0..4).map(|s| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(s); + u32::from_be_bytes(buffer) + }) +} + +/// Wrapper around an EVM input slice. +#[derive(Clone, Copy, Debug)] +pub struct Reader<'inner> { + input: &'inner [u8], + cursor: usize, +} + +impl<'inner> Reader<'inner> { + /// Create a Reader. + pub fn new(input: &'inner [u8]) -> Self { + Self { input, cursor: 0 } + } + + /// Create a Reader while skipping an initial selector. + pub fn new_skip_selector(input: &'inner [u8]) -> MayRevert { + if input.len() < 4 { + return Err(RevertReason::read_out_of_bounds("selector").into()); + } + + Ok(Self::new(&input[4..])) + } + + /// Check the input has at least the correct amount of arguments before the end (32 bytes values). + pub fn expect_arguments(&self, args: usize) -> MayRevert { + if self.input.len() >= self.cursor + args * 32 { + Ok(()) + } else { + Err(RevertReason::ExpectedAtLeastNArguments(args).into()) + } + } + + /// Read data from the input. + pub fn read(&mut self) -> MayRevert { + T::read(self) + } + + /// Read raw bytes from the input. + /// Doesn't handle any alignment checks, prefer using `read` instead of possible. + /// Returns an error if trying to parse out of bounds. + pub fn read_raw_bytes(&mut self, len: usize) -> MayRevert<&[u8]> { + let range = self.move_cursor(len)?; + + let data = self + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds("raw bytes"))?; + + Ok(data) + } + + /// Reads a pointer, returning a reader targetting the pointed location. + pub fn read_pointer(&mut self) -> MayRevert { + let offset: usize = self + .read::() + .map_err(|_| RevertReason::read_out_of_bounds("pointer"))? + .try_into() + .map_err(|_| RevertReason::value_is_too_large("pointer"))?; + + if offset >= self.input.len() { + return Err(RevertReason::PointerToOutofBound.into()); + } + + Ok(Self { + input: &self.input[offset..], + cursor: 0, + }) + } + + /// Read remaining bytes + pub fn read_till_end(&mut self) -> MayRevert<&[u8]> { + let range = self.move_cursor(self.input.len() - self.cursor)?; + + let data = self + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds("raw bytes"))?; + + Ok(data) + } + + /// Move the reading cursor with provided length, and return a range from the previous cursor + /// location to the new one. + /// Checks cursor overflows. + fn move_cursor(&mut self, len: usize) -> MayRevert> { + let start = self.cursor; + let end = self + .cursor + .checked_add(len) + .ok_or(RevertReason::CursorOverflow)?; + + self.cursor = end; + + Ok(start..end) + } +} + +/// Help build an EVM input/output data. +/// +/// Functions takes `self` to allow chaining all calls like +/// `Writer::new().write(...).write(...).build()`. +/// While it could be more ergonomic to take &mut self, this would +/// prevent to have a `build` function that don't clone the output. +#[derive(Clone, Debug, Default)] +pub struct Writer { + pub(crate) data: Vec, + offset_data: Vec, + selector: Option, +} + +#[derive(Clone, Debug)] +struct OffsetChunk { + // Offset location in the container data. + offset_position: usize, + // Data pointed by the offset that must be inserted at the end of container data. + data: Vec, + // Inside of arrays, the offset is not from the start of array data (length), but from the start + // of the item. This shift allow to correct this. + offset_shift: usize, +} + +impl Writer { + /// Creates a new empty output builder (without selector). + pub fn new() -> Self { + Default::default() + } + + /// Creates a new empty output builder with provided selector. + /// Selector will only be appended before the data when calling + /// `build` to not mess with the offsets. + pub fn new_with_selector(selector: impl Into) -> Self { + Self { + data: vec![], + offset_data: vec![], + selector: Some(selector.into()), + } + } + + // Return the built data. + pub fn build(mut self) -> Vec { + Self::bake_offsets(&mut self.data, self.offset_data); + + if let Some(selector) = self.selector { + let mut output = selector.to_be_bytes().to_vec(); + output.append(&mut self.data); + output + } else { + self.data + } + } + + /// Add offseted data at the end of this writer's data, updating the offsets. + fn bake_offsets(output: &mut Vec, offsets: Vec) { + for mut offset_chunk in offsets { + let offset_position = offset_chunk.offset_position; + let offset_position_end = offset_position + 32; + + // The offset is the distance between the start of the data and the + // start of the pointed data (start of a struct, length of an array). + // Offsets in inner data are relative to the start of their respective "container". + // However in arrays the "container" is actually the item itself instead of the whole + // array, which is corrected by `offset_shift`. + let free_space_offset = output.len() - offset_chunk.offset_shift; + + // Override dummy offset to the offset it will be in the final output. + U256::from(free_space_offset) + .to_big_endian(&mut output[offset_position..offset_position_end]); + + // Append this data at the end of the current output. + output.append(&mut offset_chunk.data); + } + } + + /// Write arbitrary bytes. + /// Doesn't handle any alignement checks, prefer using `write` instead if possible. + fn write_raw_bytes(mut self, value: &[u8]) -> Self { + self.data.extend_from_slice(value); + self + } + + /// Write data of requested type. + pub fn write(mut self, value: T) -> Self { + T::write(&mut self, value); + self + } + + /// Writes a pointer to given data. + /// The data will be appended when calling `build`. + /// Initially write a dummy value as offset in this writer's data, which will be replaced by + /// the correct offset once the pointed data is appended. + /// + /// Takes `&mut self` since its goal is to be used inside `solidity::Codec` impl and not in chains. + pub fn write_pointer(&mut self, data: Vec) { + let offset_position = self.data.len(); + H256::write(self, H256::repeat_byte(0xff)); + + self.offset_data.push(OffsetChunk { + offset_position, + data, + offset_shift: 0, + }); + } +} + +/// Adapter to parse data as a first type then convert it to another one. +/// Useful for old precompiles in which Solidity arguments where set larger than +/// the needed Rust type. +#[derive(Clone, Copy, Debug)] +pub struct Convert { + inner: C, + _phantom: PhantomData

, +} + +impl From for Convert { + fn from(value: C) -> Self { + Self { + inner: value, + _phantom: PhantomData, + } + } +} + +impl Convert { + pub fn converted(self) -> C { + self.inner + } +} + +impl Codec for Convert +where + P: Codec + TryInto, + C: Codec + Into

, +{ + fn read(reader: &mut Reader) -> MayRevert { + let c = P::read(reader)? + .try_into() + .map_err(|_| RevertReason::value_is_too_large(C::signature()))?; + + Ok(Self { + inner: c, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut Writer, value: Self) { + P::write(writer, value.inner.into()) + } + + fn has_static_size() -> bool { + P::has_static_size() + } + + fn signature() -> String { + P::signature() + } +} diff --git a/precompiles/utils_v2/src/solidity/codec/native.rs b/precompiles/utils_v2/src/solidity/codec/native.rs new file mode 100644 index 0000000000..e99f452ac3 --- /dev/null +++ b/precompiles/utils_v2/src/solidity/codec/native.rs @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use crate::solidity::revert::InjectBacktrace; +use impl_trait_for_tuples::impl_for_tuples; +use sp_core::{ConstU32, Get, H160}; + +impl Codec for () { + fn read(_reader: &mut Reader) -> MayRevert { + Ok(()) + } + + fn write(_writer: &mut Writer, _value: Self) {} + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + String::from("()") + } +} + +#[impl_for_tuples(1, 18)] +impl Codec for Tuple { + fn has_static_size() -> bool { + for_tuples!(#( Tuple::has_static_size() )&*) + } + + fn read(reader: &mut Reader) -> MayRevert { + if Self::has_static_size() { + let mut index = 0; + Ok(for_tuples!( ( #( { + let elem = reader.read::().in_tuple(index)?; + index +=1; + elem + } ),* ) )) + } else { + let reader = &mut reader.read_pointer()?; + let mut index = 0; + Ok(for_tuples!( ( #( { + let elem = reader.read::().in_tuple(index)?; + index +=1; + elem + } ),* ) )) + } + } + + fn write(writer: &mut Writer, value: Self) { + if Self::has_static_size() { + for_tuples!( #( Tuple::write(writer, value.Tuple); )* ); + } else { + let mut inner_writer = Writer::new(); + for_tuples!( #( Tuple::write(&mut inner_writer, value.Tuple); )* ); + writer.write_pointer(inner_writer.build()); + } + } + + fn signature() -> String { + let mut subtypes = Vec::new(); + for_tuples!( #( subtypes.push(Tuple::signature()); )* ); + alloc::format!("({})", subtypes.join(",")) + } + + fn is_explicit_tuple() -> bool { + true + } +} + +impl Codec for H256 { + fn read(reader: &mut Reader) -> MayRevert { + let range = reader.move_cursor(32)?; + + let data = reader + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds("bytes32"))?; + + Ok(H256::from_slice(data)) + } + + fn write(writer: &mut Writer, value: Self) { + writer.data.extend_from_slice(value.as_bytes()); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + String::from("bytes32") + } +} + +/// The `address` type of Solidity. +/// H160 could represent 2 types of data (bytes20 and address) that are not encoded the same way. +/// To avoid issues writing H160 is thus not supported. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct Address(pub H160); + +impl From for Address { + fn from(a: H160) -> Address { + Address(a) + } +} + +impl From

for H160 { + fn from(a: Address) -> H160 { + a.0 + } +} + +impl Address { + pub fn as_u64(&self) -> Option { + let _u64 = self.0.to_low_u64_be(); + if self.0 == H160::from_low_u64_be(_u64) { + Some(_u64) + } else { + None + } + } +} + +impl Codec for Address { + fn read(reader: &mut Reader) -> MayRevert { + let range = reader.move_cursor(32)?; + + let data = reader + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds("address"))?; + + Ok(H160::from_slice(&data[12..32]).into()) + } + + fn write(writer: &mut Writer, value: Self) { + H256::write(writer, value.0.into()); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + String::from("address") + } +} + +impl Codec for U256 { + fn read(reader: &mut Reader) -> MayRevert { + let range = reader.move_cursor(32)?; + + let data = reader + .input + .get(range) + .ok_or_else(|| RevertReason::read_out_of_bounds("uint256"))?; + + Ok(U256::from_big_endian(data)) + } + + fn write(writer: &mut Writer, value: Self) { + let mut buffer = [0u8; 32]; + value.to_big_endian(&mut buffer); + writer.data.extend_from_slice(&buffer); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + String::from("uint256") + } +} + +macro_rules! impl_evmdata_for_uints { + ($($uint:ty, )*) => { + $( + impl Codec for $uint { + fn read(reader: &mut Reader) -> MayRevert { + let value256: U256 = reader.read() + .map_err(|_| RevertReason::read_out_of_bounds( + Self::signature() + ))?; + + value256 + .try_into() + .map_err(|_| RevertReason::value_is_too_large( + Self::signature() + ).into()) + } + + fn write(writer: &mut Writer, value: Self) { + U256::write(writer, value.into()); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + alloc::format!("uint{}", core::mem::size_of::() * 8) + } + } + )* + }; +} + +impl_evmdata_for_uints!(u8, u16, u32, u64, u128,); + +impl Codec for bool { + fn read(reader: &mut Reader) -> MayRevert { + let h256 = H256::read(reader).map_err(|_| RevertReason::read_out_of_bounds("bool"))?; + + Ok(!h256.is_zero()) + } + + fn write(writer: &mut Writer, value: Self) { + let mut buffer = [0u8; 32]; + if value { + buffer[31] = 1; + } + + writer.data.extend_from_slice(&buffer); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + String::from("bool") + } +} + +type ConstU32Max = ConstU32<{ u32::MAX }>; + +impl Codec for Vec { + fn read(reader: &mut Reader) -> MayRevert { + BoundedVec::::read(reader).map(|x| x.into()) + } + + fn write(writer: &mut Writer, value: Self) { + BoundedVec::::write( + writer, + BoundedVec { + inner: value, + _phantom: PhantomData, + }, + ) + } + + fn has_static_size() -> bool { + false + } + + fn signature() -> String { + alloc::format!("{}[]", T::signature()) + } +} + +/// Wrapper around a Vec that provides a max length bound on read. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BoundedVec { + inner: Vec, + _phantom: PhantomData, +} + +impl> Codec for BoundedVec { + fn read(reader: &mut Reader) -> MayRevert { + let mut inner_reader = reader.read_pointer()?; + + let array_size: usize = inner_reader + .read::() + .map_err(|_| RevertReason::read_out_of_bounds("length"))? + .try_into() + .map_err(|_| RevertReason::value_is_too_large("length"))?; + + if array_size > S::get() as usize { + return Err(RevertReason::value_is_too_large("length").into()); + } + + let mut array = vec![]; + + let mut item_reader = Reader { + input: inner_reader + .input + .get(32..) + .ok_or_else(|| RevertReason::read_out_of_bounds("array content"))?, + cursor: 0, + }; + + for i in 0..array_size { + array.push(item_reader.read().in_array(i)?); + } + + Ok(BoundedVec { + inner: array, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut Writer, value: Self) { + let value: Vec<_> = value.into(); + let mut inner_writer = Writer::new().write(U256::from(value.len())); + + for inner in value { + // Any offset in items are relative to the start of the item instead of the + // start of the array. However if there is offseted data it must but appended after + // all items (offsets) are written. We thus need to rely on `compute_offsets` to do + // that, and must store a "shift" to correct the offsets. + let shift = inner_writer.data.len(); + let item_writer = Writer::new().write(inner); + + inner_writer = inner_writer.write_raw_bytes(&item_writer.data); + for mut offset_datum in item_writer.offset_data { + offset_datum.offset_shift += 32; + offset_datum.offset_position += shift; + inner_writer.offset_data.push(offset_datum); + } + } + + writer.write_pointer(inner_writer.build()); + } + + fn has_static_size() -> bool { + false + } + + fn signature() -> String { + alloc::format!("{}[]", T::signature()) + } +} + +impl From> for BoundedVec { + fn from(value: Vec) -> Self { + BoundedVec { + inner: value, + _phantom: PhantomData, + } + } +} + +impl From<&[T]> for BoundedVec { + fn from(value: &[T]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[T; N]> for BoundedVec { + fn from(value: [T; N]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.inner + } +} diff --git a/precompiles/utils_v2/src/solidity/mod.rs b/precompiles/utils_v2/src/solidity/mod.rs new file mode 100644 index 0000000000..fa45c50ff9 --- /dev/null +++ b/precompiles/utils_v2/src/solidity/mod.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Provides utilities for compatibility with Solidity tooling. + +pub mod codec; +pub mod modifier; +pub mod revert; + +pub use codec::{ + decode_arguments, decode_event_data, decode_return_value, encode_arguments, encode_event_data, + encode_return_value, encode_with_selector, Codec, +}; diff --git a/precompiles/utils_v2/src/solidity/modifier.rs b/precompiles/utils_v2/src/solidity/modifier.rs new file mode 100644 index 0000000000..0bae1bf984 --- /dev/null +++ b/precompiles/utils_v2/src/solidity/modifier.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Provide checks related to function modifiers (view/payable). + +use crate::solidity::revert::{MayRevert, RevertReason}; +use fp_evm::Context; +use sp_core::U256; + +/// Represents modifiers a Solidity function can be annotated with. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum FunctionModifier { + /// Function that doesn't modify the state. + View, + /// Function that modifies the state but refuse receiving funds. + /// Correspond to a Solidity function with no modifiers. + NonPayable, + /// Function that modifies the state and accept funds. + Payable, +} + +/// Check that a function call is compatible with the context it is +/// called into. +pub fn check_function_modifier( + context: &Context, + is_static: bool, + modifier: FunctionModifier, +) -> MayRevert { + if is_static && modifier != FunctionModifier::View { + return Err( + RevertReason::custom("Can't call non-static function in static context").into(), + ); + } + + if modifier != FunctionModifier::Payable && context.apparent_value > U256::zero() { + return Err(RevertReason::custom("Function is not payable").into()); + } + + Ok(()) +} diff --git a/precompiles/utils_v2/src/solidity/revert.rs b/precompiles/utils_v2/src/solidity/revert.rs new file mode 100644 index 0000000000..8269a83706 --- /dev/null +++ b/precompiles/utils_v2/src/solidity/revert.rs @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Utilities to work with revert messages with support for backtraces and +//! consistent formatting. + +use crate::solidity::{self, codec::bytes::UnboundedBytes}; +use alloc::string::{String, ToString}; +use fp_evm::{ExitRevert, PrecompileFailure}; +use sp_std::vec::Vec; + +/// Represent the result of a computation that can revert. +pub type MayRevert = Result; + +/// Generate an encoded revert from a simple String. +/// Returns a `PrecompileFailure` that fits in an `EvmResult::Err`. +pub fn revert(msg: impl Into) -> PrecompileFailure { + RevertReason::custom(msg).into() +} + +/// Generate an encoded revert from a simple String. +/// Returns a `Vec` in case `PrecompileFailure` is too high level. +pub fn revert_as_bytes(msg: impl Into) -> Vec { + Revert::new(RevertReason::custom(msg)).to_encoded_bytes() +} + +/// Generic error to build abi-encoded revert output. +/// See: https://docs.soliditylang.org/en/latest/control-structures.html?highlight=revert#revert +pub const ERROR_SELECTOR: u32 = 0x08c379a0; + +#[derive(Clone, PartialEq, Eq)] +enum BacktracePart { + Field(String), + Tuple(usize), + Array(usize), +} + +/// Backtrace of an revert. +/// Built depth-first. +/// Implement `Display` to render the backtrace as a string. +#[derive(Default, PartialEq, Eq)] +pub struct Backtrace(Vec); + +impl Backtrace { + /// Create a new empty backtrace. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Check if the backtrace is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl core::fmt::Display for Backtrace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + for (i, part) in self.0.iter().rev().enumerate() { + match (i, part) { + (0, BacktracePart::Field(field)) => write!(f, "{field}")?, + (_, BacktracePart::Field(field)) => write!(f, ".{field}")?, + (_, BacktracePart::Tuple(index)) => write!(f, ".{index}")?, + (_, BacktracePart::Array(index)) => write!(f, "[{index}]")?, + } + } + Ok(()) + } +} + +/// Possible revert reasons. +#[non_exhaustive] +#[derive(PartialEq, Eq)] +pub enum RevertReason { + /// A custom revert reason if other variants are not appropriate. + Custom(String), + /// Tried to read data out of bounds. + ReadOutOfBounds { + /// What was being read? + what: String, + }, + /// An unknown selector has been provided. + UnknownSelector, + /// A value is too large to fit in the wanted type. + /// For security reasons integers are always parsed as `uint256` then + /// casted to the wanted type. If the value overflows this type then this + /// revert is used. + ValueIsTooLarge { + /// What was being read? + what: String, + }, + /// A pointer (used for structs and arrays) points out of bounds. + PointerToOutofBound, + /// The reading cursor overflowed. + /// This should realistically never happen as it would require an input + /// of length larger than 2^64, which would cost too much to be included + /// in a block. + CursorOverflow, + /// Used by a check that the input contains at least N static arguments. + /// Often use to return early if the input is too short. + ExpectedAtLeastNArguments(usize), +} + +impl RevertReason { + /// Create a `RevertReason::Custom` from anything that can be converted to a `String`. + /// Argument is the custom revert message. + pub fn custom(s: impl Into) -> Self { + RevertReason::Custom(s.into()) + } + + /// Create a `RevertReason::ReadOutOfBounds` from anything that can be converted to a `String`. + /// Argument names what was expected to be read. + pub fn read_out_of_bounds(what: impl Into) -> Self { + RevertReason::ReadOutOfBounds { what: what.into() } + } + + /// Create a `RevertReason::ValueIsTooLarge` from anything that can be converted to a `String`. + /// Argument names what was expected to be read. + pub fn value_is_too_large(what: impl Into) -> Self { + RevertReason::ValueIsTooLarge { what: what.into() } + } +} + +impl core::fmt::Display for RevertReason { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + match self { + RevertReason::Custom(s) => write!(f, "{s}"), + RevertReason::ReadOutOfBounds { what } => { + write!(f, "Tried to read {what} out of bounds") + } + RevertReason::UnknownSelector => write!(f, "Unknown selector"), + RevertReason::ValueIsTooLarge { what } => write!(f, "Value is too large for {what}"), + RevertReason::PointerToOutofBound => write!(f, "Pointer points to out of bound"), + RevertReason::CursorOverflow => write!(f, "Reading cursor overflowed"), + RevertReason::ExpectedAtLeastNArguments(n) => { + write!(f, "Expected at least {n} arguments") + } + } + } +} + +/// An revert returned by various functions in precompile-utils. +/// Allows to dynamically construct the backtrace (backtrace) of the revert +/// and manage it in a typed way. +/// Can be transformed into a `PrecompileFailure::Revert` and `String`, and +/// implement `Display` and `Debug`. +#[derive(PartialEq, Eq)] +pub struct Revert { + reason: RevertReason, + backtrace: Backtrace, +} + +impl Revert { + /// Create a new `Revert` with a `RevertReason` and + /// an empty backtrace. + pub fn new(reason: RevertReason) -> Self { + Self { + reason, + backtrace: Backtrace::new(), + } + } + + /// For all `RevertReason` variants that have a `what` field, change its value. + /// Otherwise do nothing. + /// It is useful when writing custom types `solidity::Codec` implementations using + /// simpler types. + pub fn change_what(mut self, what: impl Into) -> Self { + let what = what.into(); + + self.reason = match self.reason { + RevertReason::ReadOutOfBounds { .. } => RevertReason::ReadOutOfBounds { what }, + RevertReason::ValueIsTooLarge { .. } => RevertReason::ValueIsTooLarge { what }, + other => other, + }; + + self + } + + /// Transforms the revert into its bytes representation (from a String). + pub fn to_encoded_bytes(self) -> Vec { + let bytes: Vec = self.into(); + solidity::encode_with_selector(ERROR_SELECTOR, UnboundedBytes::from(bytes)) + } +} + +impl From for Revert { + fn from(a: RevertReason) -> Revert { + Revert::new(a) + } +} + +impl core::fmt::Display for Revert { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + if !self.backtrace.is_empty() { + write!(f, "{}: ", self.backtrace)?; + } + + write!(f, "{}", self.reason) + } +} + +impl core::fmt::Debug for Revert { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!(f, "{}", self) + } +} + +impl From for Vec { + fn from(val: Revert) -> Self { + val.to_string().into() + } +} + +/// Allows to inject backtrace data. +pub trait InjectBacktrace { + /// Output type of the injection. + /// Should be a type that can hold a backtrace. + type Output; + + /// Occurs in a field. + fn in_field(self, field: impl Into) -> Self::Output; + + /// Occurs in a tuple. + fn in_tuple(self, index: usize) -> Self::Output; + + /// Occurs in an array at provided index. + fn in_array(self, index: usize) -> Self::Output; +} + +/// Additional function for everything having a Backtrace. +pub trait BacktraceExt { + /// Map last tuple entry into a field. + /// Does nothing if last entry is not a tuple. + /// As in Solidity structs are equivalent to tuples and are tricky to parse correctly, + /// it allows to parse any struct as a tuple (with the correct implementation in this crate) and + /// then map tuple indices to struct fields. + fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self; +} + +/// Additional functions for Revert and MayRevert. +pub trait RevertExt { + /// Map the reason while keeping the same backtrace. + fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self; +} + +impl InjectBacktrace for RevertReason { + // `RevertReason` cannot hold a backtrace, thus it wraps + // it into a `Revert`. + type Output = Revert; + + fn in_field(self, field: impl Into) -> Revert { + Revert::new(self).in_field(field) + } + + fn in_array(self, index: usize) -> Revert { + Revert::new(self).in_array(index) + } + + fn in_tuple(self, index: usize) -> Revert { + Revert::new(self).in_tuple(index) + } +} + +impl InjectBacktrace for Backtrace { + type Output = Self; + + fn in_field(mut self, field: impl Into) -> Self { + self.0.push(BacktracePart::Field(field.into())); + self + } + + fn in_array(mut self, index: usize) -> Self { + self.0.push(BacktracePart::Array(index)); + self + } + + fn in_tuple(mut self, index: usize) -> Self { + self.0.push(BacktracePart::Tuple(index)); + self + } +} + +impl BacktraceExt for Backtrace { + fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self { + if let Some(entry) = self.0.last_mut() { + if let BacktracePart::Tuple(index) = *entry { + if let Some(field) = fields.get(index) { + *entry = BacktracePart::Field(field.to_string()) + } + } + } + self + } +} + +impl InjectBacktrace for Revert { + type Output = Self; + + fn in_field(mut self, field: impl Into) -> Self { + self.backtrace = self.backtrace.in_field(field); + self + } + + fn in_array(mut self, index: usize) -> Self { + self.backtrace = self.backtrace.in_array(index); + self + } + + fn in_tuple(mut self, index: usize) -> Self { + self.backtrace = self.backtrace.in_tuple(index); + self + } +} + +impl RevertExt for Revert { + fn map_reason(mut self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self { + self.reason = f(self.reason); + self + } +} + +impl BacktraceExt for Revert { + fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self { + self.backtrace = self.backtrace.map_in_tuple_to_field(fields); + self + } +} + +impl InjectBacktrace for MayRevert { + type Output = Self; + + fn in_field(self, field: impl Into) -> Self { + self.map_err(|e| e.in_field(field)) + } + + fn in_array(self, index: usize) -> Self { + self.map_err(|e| e.in_array(index)) + } + + fn in_tuple(self, index: usize) -> Self { + self.map_err(|e| e.in_tuple(index)) + } +} + +impl RevertExt for MayRevert { + fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self { + self.map_err(|e| e.map_reason(f)) + } +} + +impl BacktraceExt for MayRevert { + fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self { + self.map_err(|e| e.map_in_tuple_to_field(fields)) + } +} + +impl From for PrecompileFailure { + fn from(err: Revert) -> Self { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: err.to_encoded_bytes(), + } + } +} + +impl From for PrecompileFailure { + fn from(err: RevertReason) -> Self { + Revert::new(err).into() + } +} diff --git a/precompiles/utils_v2/src/substrate.rs b/precompiles/utils_v2/src/substrate.rs new file mode 100644 index 0000000000..5873afaaac --- /dev/null +++ b/precompiles/utils_v2/src/substrate.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Utils related to Substrate features: +//! - Substrate call dispatch. +//! - Substrate DB read and write costs + +use core::marker::PhantomData; + +// Substrate +use frame_support::{ + dispatch::{GetDispatchInfo, PostDispatchInfo}, + traits::Get, + weights::Weight, +}; +use sp_runtime::{traits::Dispatchable, DispatchError}; +// Frontier +use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; +use pallet_evm::GasWeightMapping; + +use crate::{evm::handle::using_precompile_handle, solidity::revert::revert}; + +#[derive(Debug)] +pub enum TryDispatchError { + Evm(ExitError), + Substrate(DispatchError), +} + +impl From for PrecompileFailure { + fn from(f: TryDispatchError) -> PrecompileFailure { + match f { + TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e }, + TryDispatchError::Substrate(e) => { + revert(alloc::format!("Dispatched call failed with error: {e:?}")) + } + } + } +} + +/// Helper functions requiring a Substrate runtime. +/// This runtime must of course implement `pallet_evm::Config`. +#[derive(Clone, Copy, Debug)] +pub struct RuntimeHelper(PhantomData); + +impl RuntimeHelper +where + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, +{ + #[inline(always)] + pub fn record_weight_v2_cost( + handle: &mut impl PrecompileHandle, + weight: Weight, + ) -> Result<(), ExitError> { + // Make sure there is enough gas. + let remaining_gas = handle.remaining_gas(); + let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight); + if required_gas > remaining_gas { + return Err(ExitError::OutOfGas); + } + + // Make sure there is enough remaining weight + // TODO: record ref time when precompile will be benchmarked + handle.record_external_cost(None, Some(weight.proof_size())) + } + + #[inline(always)] + pub fn refund_weight_v2_cost( + handle: &mut impl PrecompileHandle, + weight: Weight, + maybe_actual_weight: Option, + ) -> Result { + // Refund weights and compute used weight them record used gas + // TODO: refund ref time when precompile will be benchmarked + let used_weight = if let Some(actual_weight) = maybe_actual_weight { + let refund_weight = weight.checked_sub(&actual_weight).unwrap_or_default(); + handle.refund_external_cost(None, Some(refund_weight.proof_size())); + actual_weight + } else { + weight + }; + let used_gas = Runtime::GasWeightMapping::weight_to_gas(used_weight); + handle.record_cost(used_gas)?; + Ok(used_gas) + } + + /// Try to dispatch a Substrate call. + /// Return an error if there are not enough gas, or if the call fails. + /// If successful returns the used gas using the Runtime GasWeightMapping. + pub fn try_dispatch( + handle: &mut impl PrecompileHandle, + origin: ::RuntimeOrigin, + call: Call, + ) -> Result + where + Runtime::RuntimeCall: From, + { + let call = Runtime::RuntimeCall::from(call); + let dispatch_info = call.get_dispatch_info(); + + Self::record_weight_v2_cost(handle, dispatch_info.weight).map_err(TryDispatchError::Evm)?; + + // Dispatch call. + // It may be possible to not record gas cost if the call returns Pays::No. + // However while Substrate handle checking weight while not making the sender pay for it, + // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered + // computations. + let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin)) + .map_err(|e| TryDispatchError::Substrate(e.error))?; + + Self::refund_weight_v2_cost( + handle, + dispatch_info.weight, + post_dispatch_info.actual_weight, + ) + .map_err(TryDispatchError::Evm)?; + + Ok(post_dispatch_info) + } +} + +impl RuntimeHelper +where + Runtime: pallet_evm::Config, +{ + /// Cost of a Substrate DB write in gas. + pub fn db_write_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().writes(1), + ) + } + + /// Cost of a Substrate DB read in gas. + pub fn db_read_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().reads(1), + ) + } +} diff --git a/precompiles/utils_v2/src/testing/account.rs b/precompiles/utils_v2/src/testing/account.rs new file mode 100644 index 0000000000..ec9bcf964a --- /dev/null +++ b/precompiles/utils_v2/src/testing/account.rs @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use pallet_evm::AddressMapping; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}; + +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + TypeInfo, + Serialize, + Deserialize, + derive_more::Display, +)] +pub struct MockAccount(pub H160); + +impl MockAccount { + pub fn from_u64(v: u64) -> Self { + H160::from_low_u64_be(v).into() + } + + pub fn zero() -> Self { + H160::zero().into() + } + + pub fn has_prefix(&self, prefix: &[u8]) -> bool { + &self.0[0..4] == prefix + } + + pub fn has_prefix_u32(&self, prefix: u32) -> bool { + self.0[0..4] == prefix.to_be_bytes() + } + + pub fn without_prefix(&self) -> u128 { + u128::from_be_bytes(<[u8; 16]>::try_from(&self.0[4..20]).expect("slice have len 16")) + } +} + +impl From for H160 { + fn from(account: MockAccount) -> H160 { + account.0 + } +} + +impl From for [u8; 20] { + fn from(account: MockAccount) -> [u8; 20] { + let x: H160 = account.into(); + x.into() + } +} + +impl From for H256 { + fn from(x: MockAccount) -> H256 { + let x: H160 = x.into(); + x.into() + } +} + +impl From for MockAccount { + fn from(address: H160) -> MockAccount { + MockAccount(address) + } +} + +impl From<[u8; 20]> for MockAccount { + fn from(address: [u8; 20]) -> MockAccount { + let x: H160 = address.into(); + MockAccount(x) + } +} + +impl AddressMapping for MockAccount { + fn into_account_id(address: H160) -> MockAccount { + address.into() + } +} + +impl sp_runtime::traits::Convert for MockAccount { + fn convert(address: H160) -> MockAccount { + address.into() + } +} + +#[macro_export] +macro_rules! mock_account { + ($name:ident, $convert:expr) => { + pub struct $name; + mock_account!(# $name, $convert); + }; + ($name:ident ( $($field:ty),* ), $convert:expr) => { + pub struct $name($(pub $field),*); + mock_account!(# $name, $convert); + }; + (# $name:ident, $convert:expr) => { + impl From<$name> for MockAccount { + fn from(value: $name) -> MockAccount { + $convert(value) + } + } + + impl From<$name> for sp_core::H160 { + fn from(value: $name) -> sp_core::H160 { + MockAccount::from(value).into() + } + } + + impl From<$name> for sp_core::H256 { + fn from(value: $name) -> sp_core::H256 { + MockAccount::from(value).into() + } + } + }; +} + +mock_account!(Zero, |_| MockAccount::zero()); +mock_account!(Alice, |_| H160::repeat_byte(0xAA).into()); +mock_account!(Bob, |_| H160::repeat_byte(0xBB).into()); +mock_account!(Charlie, |_| H160::repeat_byte(0xCC).into()); +mock_account!(David, |_| H160::repeat_byte(0xDD).into()); + +mock_account!(Precompile1, |_| MockAccount::from_u64(1)); + +mock_account!(CryptoAlith, |_| H160::from(hex_literal::hex!( + "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac" +)) +.into()); +mock_account!(CryptoBaltathar, |_| H160::from(hex_literal::hex!( + "3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0" +)) +.into()); +mock_account!(CryptoCarleth, |_| H160::from(hex_literal::hex!( + "798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc" +)) +.into()); + +mock_account!( + AddressInPrefixedSet(u32, u128), + |value: AddressInPrefixedSet| { + let prefix: u32 = value.0; + let index: u128 = value.1; + + let mut buffer = Vec::with_capacity(20); // 160 bits + + buffer.extend_from_slice(&prefix.to_be_bytes()); + buffer.extend_from_slice(&index.to_be_bytes()); + + assert_eq!(buffer.len(), 20, "address buffer should have len of 20"); + + H160::from_slice(&buffer).into() + } +); + +pub fn alith_secret_key() -> [u8; 32] { + hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") +} + +pub fn baltathar_secret_key() -> [u8; 32] { + hex_literal::hex!("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") +} + +pub fn charleth_secret_key() -> [u8; 32] { + hex_literal::hex!("0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b") +} diff --git a/precompiles/utils_v2/src/testing/execution.rs b/precompiles/utils_v2/src/testing/execution.rs new file mode 100644 index 0000000000..e37c207557 --- /dev/null +++ b/precompiles/utils_v2/src/testing/execution.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + solidity::codec::Codec, + testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, +}; +use fp_evm::{ + Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, PrecompileResult, + PrecompileSet, +}; +use sp_core::{H160, U256}; +use sp_std::boxed::Box; + +#[must_use] +pub struct PrecompilesTester<'p, P> { + precompiles: &'p P, + handle: MockHandle, + + target_gas: Option, + subcall_handle: Option, + + expected_cost: Option, + expected_logs: Option>, + static_call: bool, +} + +impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { + pub fn new( + precompiles: &'p P, + from: impl Into, + to: impl Into, + data: Vec, + ) -> Self { + let to = to.into(); + let mut handle = MockHandle::new( + to, + Context { + address: to, + caller: from.into(), + apparent_value: U256::zero(), + }, + ); + + handle.input = data; + + Self { + precompiles, + handle, + + target_gas: None, + subcall_handle: None, + + expected_cost: None, + expected_logs: None, + static_call: false, + } + } + + pub fn with_value(mut self, value: impl Into) -> Self { + self.handle.context.apparent_value = value.into(); + self + } + + pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { + self.subcall_handle = Some(Box::new(subcall_handle)); + self + } + + pub fn with_target_gas(mut self, target_gas: Option) -> Self { + self.target_gas = target_gas; + self + } + + pub fn with_static_call(mut self, static_call: bool) -> Self { + self.static_call = static_call; + self + } + + pub fn expect_cost(mut self, cost: u64) -> Self { + self.expected_cost = Some(cost); + self + } + + pub fn expect_no_logs(mut self) -> Self { + self.expected_logs = Some(vec![]); + self + } + + pub fn expect_log(mut self, log: Log) -> Self { + self.expected_logs = Some({ + let mut logs = self.expected_logs.unwrap_or_default(); + logs.push(PrettyLog(log)); + logs + }); + self + } + + fn assert_optionals(&self) { + if let Some(cost) = &self.expected_cost { + assert_eq!(&self.handle.gas_used, cost); + } + + if let Some(logs) = &self.expected_logs { + similar_asserts::assert_eq!(&self.handle.logs, logs); + } + } + + fn execute(&mut self) -> Option { + let handle = &mut self.handle; + handle.subcall_handle = self.subcall_handle.take(); + handle.is_static = self.static_call; + + if let Some(gas_limit) = self.target_gas { + handle.gas_limit = gas_limit; + } + + let res = self.precompiles.execute(handle); + + self.subcall_handle = handle.subcall_handle.take(); + + res + } + + /// Execute the precompile set and expect some precompile to have been executed, regardless of the + /// result. + pub fn execute_some(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and expect no precompile to have been executed. + pub fn execute_none(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_returns_raw(mut self, output: Vec) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Shouldn't have reverted"); + } + Some(Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: execution_output, + })) => { + if execution_output != output { + eprintln!( + "Output (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&execution_output) + ); + eprintln!( + "Output (string): {:?}", + core::str::from_utf8(&execution_output).ok() + ); + panic!("Output doesn't match"); + } + } + other => panic!("Unexpected result: {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided Solidity encoded output. + pub fn execute_returns(self, output: impl Codec) { + self.execute_returns_raw(crate::solidity::encode_return_value(output)) + } + + /// Execute the precompile set and check if it reverts. + /// Take a closure allowing to perform custom matching on the output. + pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + if !check(decoded) { + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Revert reason doesn't match !"); + } + } + other => panic!("Didn't revert, instead returned {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_error(mut self, error: ExitError) { + let res = self.execute(); + assert_eq!( + res, + Some(Err(PrecompileFailure::Error { exit_status: error })) + ); + self.assert_optionals(); + } +} + +pub trait PrecompileTesterExt: PrecompileSet + Sized { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester; +} + +impl PrecompileTesterExt for T { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester { + PrecompilesTester::new(self, from, to, data.into()) + } +} diff --git a/precompiles/utils_v2/src/testing/handle.rs b/precompiles/utils_v2/src/testing/handle.rs new file mode 100644 index 0000000000..cf34dda88c --- /dev/null +++ b/precompiles/utils_v2/src/testing/handle.rs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::testing::PrettyLog; +use evm::{ExitRevert, ExitSucceed}; +use fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}; +use sp_core::{H160, H256}; +use sp_std::boxed::Box; + +#[derive(Debug, Clone)] +pub struct Subcall { + pub address: H160, + pub transfer: Option, + pub input: Vec, + pub target_gas: Option, + pub is_static: bool, + pub context: Context, +} + +#[derive(Debug, Clone)] +pub struct SubcallOutput { + pub reason: ExitReason, + pub output: Vec, + pub cost: u64, + pub logs: Vec, +} + +impl SubcallOutput { + pub fn revert() -> Self { + Self { + reason: ExitReason::Revert(ExitRevert::Reverted), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn succeed() -> Self { + Self { + reason: ExitReason::Succeed(ExitSucceed::Returned), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn out_of_gas() -> Self { + Self { + reason: ExitReason::Error(ExitError::OutOfGas), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } +} + +pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} + +impl SubcallOutput + 'static> SubcallTrait for T {} + +pub type SubcallHandle = Box; + +/// Mock handle to write tests for precompiles. +pub struct MockHandle { + pub gas_limit: u64, + pub gas_used: u64, + pub logs: Vec, + pub subcall_handle: Option, + pub code_address: H160, + pub input: Vec, + pub context: Context, + pub is_static: bool, +} + +impl MockHandle { + pub fn new(code_address: H160, context: Context) -> Self { + Self { + gas_limit: u64::MAX, + gas_used: 0, + logs: vec![], + subcall_handle: None, + code_address, + input: Vec::new(), + context, + is_static: false, + } + } +} + +impl PrecompileHandle for MockHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec) { + if self + .record_cost(crate::evm::costs::call_cost( + context.apparent_value, + &evm::Config::london(), + )) + .is_err() + { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + match &mut self.subcall_handle { + Some(handle) => { + let SubcallOutput { + reason, + output, + cost, + logs, + } = handle(Subcall { + address, + transfer, + input, + target_gas, + is_static, + context: context.clone(), + }); + + if self.record_cost(cost).is_err() { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + for log in logs { + self.log(log.address, log.topics, log.data) + .expect("cannot fail"); + } + + (reason, output) + } + None => panic!("no subcall handle registered"), + } + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.gas_used += cost; + + if self.gas_used > self.gas_limit { + Err(ExitError::OutOfGas) + } else { + Ok(()) + } + } + + fn remaining_gas(&self) -> u64 { + self.gas_limit - self.gas_used + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + self.logs.push(PrettyLog(Log { + address, + topics, + data, + })); + Ok(()) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + &self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + &self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + Some(self.gas_limit) + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} +} diff --git a/precompiles/utils_v2/src/testing/mod.rs b/precompiles/utils_v2/src/testing/mod.rs new file mode 100644 index 0000000000..89e826a4fb --- /dev/null +++ b/precompiles/utils_v2/src/testing/mod.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod account; +pub mod execution; +pub mod handle; +pub mod modifier; +mod solidity; + +pub use account::*; +pub use execution::*; +pub use handle::*; +pub use modifier::*; +pub use solidity::{check_precompile_implements_solidity_interfaces, compute_selector}; + +use fp_evm::Log; + +pub fn decode_revert_message(encoded: &[u8]) -> &[u8] { + let encoded_len = encoded.len(); + // selector 4 + offset 32 + string length 32 + if encoded_len > 68 { + let message_len = encoded[36..68].iter().sum::(); + if encoded_len >= 68 + message_len as usize { + return &encoded[68..68 + message_len as usize]; + } + } + b"decode_revert_message: error" +} + +#[derive(Clone, PartialEq, Eq)] +pub struct PrettyLog(Log); + +impl core::fmt::Debug for PrettyLog { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let bytes = self + .0 + .data + .iter() + .map(|b| format!("{:02X}", b)) + .collect::>() + .join(""); + + let message = String::from_utf8(self.0.data.clone()).ok(); + + f.debug_struct("Log") + .field("address", &self.0.address) + .field("topics", &self.0.topics) + .field("data", &bytes) + .field("data_utf8", &message) + .finish() + } +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + $crate::mock::events() + ); + } + } + }; +} + +// Panics if an event is found in the system log of events +#[macro_export] +macro_rules! assert_event_not_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_none(), + "Event {:?} was found in events: \n {:?}", + e, + $crate::mock::events() + ); + } + } + }; +} diff --git a/precompiles/utils_v2/src/testing/modifier.rs b/precompiles/utils_v2/src/testing/modifier.rs new file mode 100644 index 0000000000..b0a795333b --- /dev/null +++ b/precompiles/utils_v2/src/testing/modifier.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + solidity::codec::Writer, + testing::{decode_revert_message, MockHandle}, +}; +use fp_evm::{Context, PrecompileFailure, PrecompileSet}; +use sp_core::{H160, U256}; + +pub struct PrecompilesModifierTester

{ + precompiles: P, + handle: MockHandle, +} + +impl PrecompilesModifierTester

{ + pub fn new(precompiles: P, from: impl Into, to: impl Into) -> Self { + let to = to.into(); + let mut handle = MockHandle::new( + to, + Context { + address: to, + caller: from.into(), + apparent_value: U256::zero(), + }, + ); + + handle.gas_limit = u64::MAX; + + Self { + precompiles, + handle, + } + } + + fn is_view(&mut self, selector: u32) -> bool { + // View: calling with static should not revert with static-related message. + let handle = &mut self.handle; + handle.is_static = true; + handle.context.apparent_value = U256::zero(); + handle.input = Writer::new_with_selector(selector).build(); + + let res = self.precompiles.execute(handle); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + + dbg!(decoded) != b"Can't call non-static function in static context" + } + Some(_) => true, + None => panic!("tried to check view modifier on unknown precompile"), + } + } + + fn is_payable(&mut self, selector: u32) -> bool { + // Payable: calling with value should not revert with payable-related message. + let handle = &mut self.handle; + handle.is_static = false; + handle.context.apparent_value = U256::one(); + handle.input = Writer::new_with_selector(selector).build(); + + let res = self.precompiles.execute(handle); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + + decoded != b"Function is not payable" + } + Some(_) => true, + None => panic!("tried to check payable modifier on unknown precompile"), + } + } + + pub fn test_view_modifier(&mut self, selectors: &[u32]) { + for &s in selectors { + assert!( + self.is_view(s), + "Function doesn't behave like a view function." + ); + assert!( + !self.is_payable(s), + "Function doesn't behave like a non-payable function." + ) + } + } + + pub fn test_payable_modifier(&mut self, selectors: &[u32]) { + for &s in selectors { + assert!( + !self.is_view(s), + "Function doesn't behave like a non-view function." + ); + assert!( + self.is_payable(s), + "Function doesn't behave like a payable function." + ); + } + } + + pub fn test_default_modifier(&mut self, selectors: &[u32]) { + for &s in selectors { + assert!( + !self.is_view(s), + "Function doesn't behave like a non-view function." + ); + assert!( + !self.is_payable(s), + "Function doesn't behave like a non-payable function." + ); + } + } +} diff --git a/precompiles/utils_v2/src/testing/solidity.rs b/precompiles/utils_v2/src/testing/solidity.rs new file mode 100644 index 0000000000..34d20c7e69 --- /dev/null +++ b/precompiles/utils_v2/src/testing/solidity.rs @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Utility module to interact with solidity file. + +use sp_io::hashing::keccak_256; +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader, Read}, +}; + +pub fn check_precompile_implements_solidity_interfaces( + files: &[&'static str], + supports_selector: F, +) where + F: Fn(u32) -> bool, +{ + for file in files { + for solidity_fn in get_selectors(file) { + assert_eq!( + solidity_fn.compute_selector_hex(), + solidity_fn.docs_selector, + "documented selector for '{}' did not match in file '{}'", + solidity_fn.signature(), + file, + ); + + let selector = solidity_fn.compute_selector(); + if !supports_selector(selector) { + panic!( + "precompile don't support selector {selector:x} for function '{}' listed in file\ + {file}", + solidity_fn.signature(), + ) + } + } + } +} + +/// Represents a declared custom type struct within a solidity file +#[derive(Clone, Default, Debug)] +pub struct SolidityStruct { + /// Struct name + pub name: String, + /// List of parameter types + pub params: Vec, + /// Is struct an enum + pub is_enum: bool, +} + +impl SolidityStruct { + /// Returns the representative signature for the solidity struct + pub fn signature(&self) -> String { + if self.is_enum { + "uint8".to_string() + } else { + format!("({})", self.params.join(",")) + } + } +} + +/// Represents a declared function within a solidity file +#[derive(Clone, Default)] +pub struct SolidityFunction { + /// Function name + pub name: String, + /// List of function parameter types + pub args: Vec, + /// The declared selector in the file + pub docs_selector: String, +} + +impl SolidityFunction { + /// Returns the representative signature for the solidity function + pub fn signature(&self) -> String { + format!("{}({})", self.name, self.args.join(",")) + } + + /// Computes the selector code for the solidity function + pub fn compute_selector(&self) -> u32 { + compute_selector(&self.signature()) + } + + /// Computes the selector code as a hex string for the solidity function + pub fn compute_selector_hex(&self) -> String { + format!("{:0>8x}", self.compute_selector()) + } +} + +/// Computes a solidity selector from a given string +pub fn compute_selector(v: &str) -> u32 { + let output = keccak_256(v.as_bytes()); + let mut buf = [0u8; 4]; + buf.clone_from_slice(&output[..4]); + u32::from_be_bytes(buf) +} + +/// Returns a list of [SolidityFunction] defined in a solidity file +pub fn get_selectors(filename: &str) -> Vec { + let file = File::open(filename) + .unwrap_or_else(|e| panic!("failed opening file '{}': {}", filename, e)); + get_selectors_from_reader(file) +} + +/// Attempts to lookup a custom struct and returns its primitive signature +fn try_lookup_custom_type(word: &str, custom_types: &HashMap) -> String { + match word.strip_suffix("[]") { + Some(word) => { + if let Some(t) = custom_types.get(word) { + return format!("{}[]", t.signature()); + } + } + None => { + if let Some(t) = custom_types.get(word) { + return t.signature(); + } + } + }; + + word.to_string() +} + +fn get_selectors_from_reader(reader: R) -> Vec { + #[derive(Clone, Copy)] + enum Stage { + Start, + Enum, + Struct, + StructParams, + FnName, + Args, + } + #[derive(Clone, Copy)] + enum Pair { + First, + Second, + } + impl Pair { + fn next(&mut self) { + *self = match self { + Pair::First => Pair::Second, + Pair::Second => Pair::First, + } + } + } + + let reader = BufReader::new(reader); + let mut functions = vec![]; + let mut custom_types = HashMap::new(); + let mut solidity_struct = SolidityStruct::default(); + + let mut stage = Stage::Start; + let mut pair = Pair::First; + let mut solidity_fn = SolidityFunction::default(); + for line in reader.lines() { + let line = line.expect("failed unwrapping line").trim().to_string(); + // identify declared selector + if line.starts_with("/// @custom:selector ") && matches!(stage, Stage::Start) { + solidity_fn.docs_selector = line.replace("/// @custom:selector ", "").to_string(); + } + + // skip comments + if line.starts_with("//") { + continue; + } + + for word in line.split(&[';', ',', '(', ')', ' ']) { + // skip whitespace + if word.trim().is_empty() { + continue; + } + match (stage, pair, word) { + // parse custom type enums + (Stage::Start, Pair::First, "enum") => { + stage = Stage::Enum; + pair.next(); + } + (Stage::Enum, Pair::Second, _) => { + custom_types.insert( + word.to_string(), + SolidityStruct { + name: word.to_string(), + is_enum: true, + params: vec![], + }, + ); + stage = Stage::Start; + pair = Pair::First; + } + + // parse custom type structs + (Stage::Start, Pair::First, "struct") => { + stage = Stage::Struct; + pair.next(); + } + (Stage::Struct, Pair::Second, _) => { + solidity_struct.name = word.to_string(); + stage = Stage::StructParams; + pair.next(); + } + (Stage::StructParams, Pair::First, "{") => (), + (Stage::StructParams, Pair::First, "}") => { + custom_types.insert(solidity_struct.name.clone(), solidity_struct); + stage = Stage::Start; + solidity_struct = SolidityStruct::default(); + } + (Stage::StructParams, Pair::First, _) => { + let param = try_lookup_custom_type(word, &custom_types); + solidity_struct.params.push(param); + pair.next(); + } + (Stage::StructParams, Pair::Second, _) => { + pair.next(); + } + + // parse function + (Stage::Start, Pair::First, "function") => { + stage = Stage::FnName; + pair.next(); + } + (Stage::FnName, Pair::Second, _) => { + solidity_fn.name = word.to_string(); + stage = Stage::Args; + pair.next(); + } + (Stage::Args, Pair::First, "external") => { + functions.push(solidity_fn); + stage = Stage::Start; + pair = Pair::First; + solidity_fn = SolidityFunction::default() + } + (Stage::Args, Pair::First, _) => { + let mut arg = word.to_string(); + arg = try_lookup_custom_type(&arg, &custom_types); + + solidity_fn.args.push(arg); + pair.next(); + } + (Stage::Args, Pair::Second, "memory" | "calldata" | "storage") => (), + (Stage::Args, Pair::Second, _) => pair.next(), + _ => { + stage = Stage::Start; + pair = Pair::First; + solidity_fn = SolidityFunction::default() + } + } + } + } + + functions +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_selectors_are_parsed() { + let actual = get_selectors("tests/solidity_test.sol") + .into_iter() + .map(|sol_fn| { + ( + sol_fn.compute_selector_hex(), + sol_fn.docs_selector.clone(), + sol_fn.signature(), + ) + }) + .collect::>(); + let expected = vec![ + ( + String::from("f7af8d91"), + String::from(""), + String::from("fnNoArgs()"), + ), + ( + String::from("d43a9a43"), + String::from("c4921133"), + String::from("fnOneArg(address)"), + ), + ( + String::from("40d6a43d"), + String::from("67ea837e"), + String::from("fnTwoArgs(address,uint256)"), + ), + ( + String::from("cee150c8"), + String::from("d6b423d9"), + String::from("fnSameArgs(uint64,uint64)"), + ), + ( + String::from("c6024207"), + String::from("b9904a86"), + String::from("fnOneArgSameLine(uint64)"), + ), + ( + String::from("fcbc04c3"), + String::from("28f0c44e"), + String::from("fnTwoArgsSameLine(uint64,bytes32)"), + ), + ( + String::from("c590304c"), + String::from("06f0c1ce"), + String::from("fnTwoArgsSameLineExternalSplit(uint64,bytes32)"), + ), + ( + String::from("a19a07e1"), + String::from("18001a4e"), + String::from("fnMemoryArrayArgs(address[],uint256[],bytes[])"), + ), + ( + String::from("ec26cf1c"), + String::from("1ea61a4e"), + String::from("fnCalldataArgs(string,bytes[])"), + ), + ( + String::from("f29f96de"), + String::from("d8af1a4e"), + String::from("fnCustomArgs((uint8,bytes[]),bytes[],uint64)"), + ), + ( + String::from("d751d651"), + String::from("e8af1642"), + String::from("fnEnumArgs(uint8,uint64)"), + ), + ( + String::from("b2c9f1a3"), + String::from("550c1a4e"), + String::from( + "fnCustomArgsMultiple((uint8,bytes[]),(address[],uint256[],bytes[]),bytes[],\ + uint64)", + ), + ), + ( + String::from("d5363eee"), + String::from("77af1a40"), + String::from("fnCustomArrayArgs((uint8,bytes[])[],bytes[])"), + ), + ( + String::from("b82da727"), + String::from("80af0a40"), + String::from( + "fnCustomComposedArg(((uint8,bytes[]),\ + (address[],uint256[],bytes[])[]),uint64)", + ), + ), + ( + String::from("586a2193"), + String::from("97baa040"), + String::from( + "fnCustomComposedArrayArg(((uint8,bytes[]),\ + (address[],uint256[],bytes[])[])[],uint64)", + ), + ), + ]; + + assert_eq!(expected, actual); + } +} diff --git a/precompiles/utils_v2/tests-external/Cargo.toml b/precompiles/utils_v2/tests-external/Cargo.toml new file mode 100644 index 0000000000..b113dd5145 --- /dev/null +++ b/precompiles/utils_v2/tests-external/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "precompile-utils-tests-external" +authors = { workspace = true } +edition = "2021" +version = "0.1.0" + +[lib] +path = "./lib.rs" + +[dependencies] +evm = { workspace = true, features = ["with-codec"] } +hex-literal = { workspace = true } +scale-codec = { package = "parity-scale-codec", workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true, features = ["derive"] } +# Substrate +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +# Substrate FRAME +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } +pallet-timestamp = { workspace = true } +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } +precompile-utils = { workspace = true, features = ["testing"] } diff --git a/precompiles/utils_v2/tests-external/lib.rs b/precompiles/utils_v2/tests-external/lib.rs new file mode 100644 index 0000000000..75199c1cee --- /dev/null +++ b/precompiles/utils_v2/tests-external/lib.rs @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg(test)] + +use std::{cell::RefCell, rc::Rc}; + +// Substrate +use frame_support::{construct_runtime, parameter_types, traits::Everything, weights::Weight}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, Perbill, +}; +// Frontier +use fp_evm::{ExitReason, ExitRevert, PrecompileFailure, PrecompileHandle}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot}; +use precompile_utils::{ + precompile_set::*, + solidity::{codec::Writer, revert::revert}, + testing::*, + EvmResult, +}; + +pub type AccountId = MockAccount; +pub type Balance = u128; + +construct_runtime!( + pub enum Runtime { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Event}, + Evm: pallet_evm::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = frame_system::mocking::MockBlock; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 0; +} +impl pallet_balances::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type ReserveIdentifier = [u8; 4]; + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxLocks = (); + type MaxReserves = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +#[derive(Debug, Clone)] +pub struct MockPrecompile; + +#[precompile_utils::precompile] +impl MockPrecompile { + // a3cab0dd + #[precompile::public("subcall()")] + fn subcall(handle: &mut impl PrecompileHandle) -> EvmResult { + match handle.call( + handle.code_address(), + None, + // calls subcallLayer2() + Writer::new_with_selector(0x0b93381bu32).build(), + None, + false, + &evm::Context { + caller: handle.code_address(), + address: handle.code_address(), + apparent_value: 0.into(), + }, + ) { + (ExitReason::Succeed(_), _) => Ok(()), + (ExitReason::Revert(_), v) => Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: v, + }), + _ => Err(revert("unexpected error")), + } + } + + // 0b93381b + #[precompile::public("success()")] + fn success(_: &mut impl PrecompileHandle) -> EvmResult { + Ok(()) + } +} + +struct MockPrecompileHandle; +impl PrecompileHandle for MockPrecompileHandle { + fn call( + &mut self, + _: H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &evm::Context, + ) -> (ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { + Ok(()) + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + ) -> Result<(), fp_evm::ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} + + fn remaining_gas(&self) -> u64 { + unimplemented!() + } + + fn log(&mut self, _: H160, _: Vec, _: Vec) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn code_address(&self) -> H160 { + unimplemented!() + } + + fn input(&self) -> &[u8] { + unimplemented!() + } + + fn context(&self) -> &evm::Context { + unimplemented!() + } + + fn is_static(&self) -> bool { + true + } + + fn gas_limit(&self) -> Option { + unimplemented!() + } +} + +pub type Precompiles = PrecompileSetBuilder< + R, + ( + PrecompileAt, MockPrecompile>, + PrecompileAt, MockPrecompile, CallableByContract>, + PrecompileAt, MockPrecompile, CallableByPrecompile>, + PrecompileAt, MockPrecompile, SubcallWithMaxNesting<1>>, + ), +>; + +pub type PCall = MockPrecompileCall; + +const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; + +parameter_types! { + pub BlockGasLimit: U256 = U256::from(u64::MAX); + pub PrecompilesValue: Precompiles = Precompiles::new(); + pub const WeightPerGas: Weight = Weight::from_parts(1, 0); + pub GasLimitPovSizeRatio: u64 = { + let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); + block_gas_limit.saturating_div(MAX_POV_SIZE) + }; + pub SuicideQuickClearLimit: u32 = 0; +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = (); + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type SuicideQuickClearLimit = SuicideQuickClearLimit; + type Timestamp = Timestamp; + type WeightInfo = pallet_evm::weights::SubstrateWeight; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +#[derive(Default)] +struct ExtBuilder {} + +impl ExtBuilder { + fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext + } +} + +fn precompiles() -> Precompiles { + PrecompilesValue::get() +} + +#[test] +fn default_checks_succeed_when_called_by_eoa() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {}) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +#[test] +fn default_checks_revert_when_called_by_precompile() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + H160::from_low_u64_be(1), + H160::from_low_u64_be(1), + PCall::success {}, + ) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_reverts(|r| r == b"Function not callable by precompiles") + }) +} + +#[test] +fn default_checks_revert_when_called_by_contract() { + ExtBuilder::default().build().execute_with(|| { + pallet_evm::Pallet::::create_account( + Alice.into(), + hex_literal::hex!("1460006000fd").to_vec(), + ); + + precompiles() + .prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {}) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_reverts(|r| r == b"Function not callable by smart contracts") + }) +} + +#[test] +fn default_checks_revert_when_doing_subcall() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test(Alice, H160::from_low_u64_be(1), PCall::subcall {}) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_reverts(|r| r == b"subcalls disabled for this precompile") + }) +} + +#[test] +fn callable_by_contract_works() { + ExtBuilder::default().build().execute_with(|| { + pallet_evm::Pallet::::create_account( + Alice.into(), + hex_literal::hex!("1460006000fd").to_vec(), + ); + + precompiles() + .prepare_test(Alice, H160::from_low_u64_be(2), PCall::success {}) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +#[test] +fn callable_by_precompile_works() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + H160::from_low_u64_be(3), + H160::from_low_u64_be(3), + PCall::success {}, + ) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +#[test] +fn subcalls_works_when_allowed() { + ExtBuilder::default().build().execute_with(|| { + let subcall_occured = Rc::new(RefCell::new(false)); + { + let subcall_occured = Rc::clone(&subcall_occured); + precompiles() + .prepare_test(Alice, H160::from_low_u64_be(4), PCall::subcall {}) + .with_subcall_handle(move |Subcall { .. }| { + *subcall_occured.borrow_mut() = true; + SubcallOutput::succeed() + }) + .execute_returns(()); + } + assert!(*subcall_occured.borrow()); + }) +} + +#[test] +fn get_address_type_works_for_eoa() { + ExtBuilder::default().build().execute_with(|| { + let addr = H160::repeat_byte(0x1d); + assert_eq!( + AddressType::EOA, + get_address_type::(&mut MockPrecompileHandle, addr).expect("OOG") + ); + }) +} + +#[test] +fn get_address_type_works_for_precompile() { + ExtBuilder::default().build().execute_with(|| { + let addr = H160::repeat_byte(0x1d); + pallet_evm::AccountCodes::::insert(addr, vec![0x60, 0x00, 0x60, 0x00, 0xfd]); + assert_eq!( + AddressType::Precompile, + get_address_type::(&mut MockPrecompileHandle, addr).expect("OOG") + ); + }) +} + +#[test] +fn get_address_type_works_for_smart_contract() { + ExtBuilder::default().build().execute_with(|| { + let addr = H160::repeat_byte(0x1d); + + // length > 5 + pallet_evm::AccountCodes::::insert( + addr, + vec![0x60, 0x00, 0x60, 0x00, 0xfd, 0xff, 0xff], + ); + assert_eq!( + AddressType::Contract, + get_address_type::(&mut MockPrecompileHandle, addr).expect("OOG") + ); + + // length < 5 + pallet_evm::AccountCodes::::insert(addr, vec![0x60, 0x00, 0x60]); + assert_eq!( + AddressType::Contract, + get_address_type::(&mut MockPrecompileHandle, addr).expect("OOG") + ); + }) +} + +#[test] +fn get_address_type_works_for_unknown() { + ExtBuilder::default().build().execute_with(|| { + let addr = H160::repeat_byte(0x1d); + pallet_evm::AccountCodes::::insert(addr, vec![0x11, 0x00, 0x60, 0x00, 0xfd]); + assert_eq!( + AddressType::Unknown, + get_address_type::(&mut MockPrecompileHandle, addr).expect("OOG") + ); + }) +} diff --git a/precompiles/utils_v2/tests/solidity_test.sol b/precompiles/utils_v2/tests/solidity_test.sol new file mode 100644 index 0000000000..bd16b8487c --- /dev/null +++ b/precompiles/utils_v2/tests/solidity_test.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @title Solidity test file with incorrectly defined selectors +interface SolidityTest { + /// A custom enum + enum CustomEnum0 { + A, + B, + C + } + + /// A custom type + struct CustomArg0 { + CustomEnum0 p0; + bytes[] p1; + } + + /// A custom type + struct CustomArg1 { + address[] p0; + uint256[] p1; + bytes[] p2; + } + + /// A composed custom type + struct CustomArg2 { + CustomArg0 p0; + CustomArg1[] p1; + } + + /// @dev Function without params and no selector + function fnNoArgs() external; + + /// @dev Function info + /// + /// @param arg0 Arg0 Description + /// @custom:selector c4921133 + function fnOneArg(address arg0) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 67ea837e + function fnTwoArgs(address arg0, uint256 arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector d6b423d9 + function fnSameArgs(uint64 arg0, uint64 arg1) external; + + /// @param arg0 Arg0 Description + /// @custom:selector b9904a86 + function fnOneArgSameLine(uint64 arg0) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 28f0c44e + function fnTwoArgsSameLine(uint64 arg0, bytes32 arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 06f0c1ce + function fnTwoArgsSameLineExternalSplit(uint64 arg0, bytes32 arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @param arg2 Arg2 Description + /// @custom:selector 18001a4e + function fnMemoryArrayArgs( + address[] memory arg0, + uint256[] memory arg1, + bytes[] memory arg2 + ) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 1ea61a4e + function fnCalldataArgs(string calldata arg0, bytes[] memory arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @param arg2 Arg2 Description + /// @custom:selector d8af1a4e + function fnCustomArgs( + CustomArg0 memory arg0, + bytes[] memory arg1, + uint64 arg2 + ) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector e8af1642 + function fnEnumArgs(CustomEnum0 arg0, uint64 arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @param arg2 Arg2 Description + /// @param arg3 Arg3 Description + /// @custom:selector 550c1a4e + function fnCustomArgsMultiple( + CustomArg0 memory arg0, + CustomArg1 memory arg1, + bytes[] memory arg2, + uint64 arg3 + ) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 77af1a40 + function fnCustomArrayArgs(CustomArg0[] memory arg0, bytes[] memory arg1) + external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 80af0a40 + function fnCustomComposedArg(CustomArg2 memory arg0, uint64 arg1) external; + + /// @param arg0 Arg0 Description + /// @param arg1 Arg1 Description + /// @custom:selector 97baa040 + function fnCustomComposedArrayArg(CustomArg2[] memory arg0, uint64 arg1) + external; +} diff --git a/runtime/astar/Cargo.toml b/runtime/astar/Cargo.toml index 3fe0360ab5..4964e74b5d 100644 --- a/runtime/astar/Cargo.toml +++ b/runtime/astar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-runtime" -version = "5.23.0" +version = "5.24.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 10cab30a18..6fe67bcf43 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -140,7 +140,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("astar"), impl_name: create_runtime_str!("astar"), authoring_version: 1, - spec_version: 70, + spec_version: 71, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 829f2ba452..6267f2260c 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shibuya-runtime" -version = "5.23.0" +version = "5.24.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index cb0ee8a1e9..fd7addf29d 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -166,7 +166,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shibuya"), impl_name: create_runtime_str!("shibuya"), authoring_version: 1, - spec_version: 113, + spec_version: 114, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/runtime/shiden/Cargo.toml b/runtime/shiden/Cargo.toml index 9153b6c11b..494097b79e 100644 --- a/runtime/shiden/Cargo.toml +++ b/runtime/shiden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shiden-runtime" -version = "5.23.0" +version = "5.24.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index ac81f8228a..ae3d7384b2 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -140,7 +140,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shiden"), impl_name: create_runtime_str!("shiden"), authoring_version: 1, - spec_version: 110, + spec_version: 111, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -1038,41 +1038,10 @@ pub type Executive = frame_executive::Executive< Migrations, >; -// Used to cleanup BaseFee storage - remove once cleanup is done. -parameter_types! { - pub const BaseFeeStr: &'static str = "BaseFee"; -} - -/// Simple `OnRuntimeUpgrade` logic to prepare Shiden runtime for `DynamicEvmBaseFee` pallet. -pub use frame_support::traits::{OnRuntimeUpgrade, StorageVersion}; -pub struct DynamicEvmBaseFeeMigration; -impl OnRuntimeUpgrade for DynamicEvmBaseFeeMigration { - fn on_runtime_upgrade() -> Weight { - // Safety check to ensure we don't execute this migration twice - if pallet_dynamic_evm_base_fee::BaseFeePerGas::::exists() { - return ::DbWeight::get().reads(1); - } - - // Set the init value to what was set before on the old `BaseFee` pallet. - pallet_dynamic_evm_base_fee::BaseFeePerGas::::put(U256::from(1_000_000_000_u128)); - - // Shiden's multiplier is so low that we have to set it to minimum value directly. - pallet_transaction_payment::NextFeeMultiplier::::put(MinimumMultiplier::get()); - - // Set init storage version for the pallet - StorageVersion::new(1).put::>(); - - ::DbWeight::get().reads_writes(1, 3) - } -} - /// All migrations that will run on the next runtime upgrade. /// /// Once done, migrations should be removed from the tuple. -pub type Migrations = ( - frame_support::migrations::RemovePallet, - DynamicEvmBaseFeeMigration, -); +pub type Migrations = (); type EventRecord = frame_system::EventRecord< ::RuntimeEvent,