diff --git a/Cargo.lock b/Cargo.lock index d25a1c6e2..e3ebaece2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "approx" @@ -2364,7 +2364,7 @@ dependencies = [ [[package]] name = "fc-consensus" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "async-trait", "fc-db", @@ -2383,7 +2383,7 @@ dependencies = [ [[package]] name = "fc-db" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-storage", "kvdb-rocksdb", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "fc-mapping-sync" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fc-db", "fp-consensus", @@ -2416,7 +2416,7 @@ dependencies = [ [[package]] name = "fc-rpc" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "ethereum-types", @@ -2455,7 +2455,7 @@ dependencies = [ [[package]] name = "fc-rpc-core" version = "1.1.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "ethereum-types", @@ -2579,7 +2579,7 @@ dependencies = [ [[package]] name = "fp-consensus" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "parity-scale-codec", @@ -2591,7 +2591,7 @@ dependencies = [ [[package]] name = "fp-dynamic-fee" version = "1.0.0" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "async-trait", "sp-core", @@ -2601,7 +2601,7 @@ dependencies = [ [[package]] name = "fp-ethereum" version = "1.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "ethereum-types", @@ -2615,7 +2615,7 @@ dependencies = [ [[package]] name = "fp-evm" version = "3.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "evm", "frame-support", @@ -2628,7 +2628,7 @@ dependencies = [ [[package]] name = "fp-rpc" version = "3.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "ethereum-types", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "fp-self-contained" version = "1.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "frame-support", @@ -2659,7 +2659,7 @@ dependencies = [ [[package]] name = "fp-storage" version = "2.0.0" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "parity-scale-codec", "serde", @@ -3205,9 +3205,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -3524,9 +3524,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b" +checksum = "ef5528d9c2817db4e10cc78f8d4c8228906e5854f389ff6b076cee3572a09d35" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3655,9 +3655,9 @@ checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" [[package]] name = "io-lifetimes" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" +checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" [[package]] name = "ip_network" @@ -3924,6 +3924,7 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-balances-erc20", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dispatch", @@ -4151,9 +4152,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.131" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libloading" @@ -4939,9 +4940,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2e4455be2010e8c5e77f0d10234b30f3a636a5305725609b5a71ad00d22577" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -5423,9 +5424,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "opaque-debug" @@ -5819,7 +5820,7 @@ dependencies = [ [[package]] name = "pallet-base-fee" version = "1.0.0" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "frame-support", @@ -6108,7 +6109,7 @@ dependencies = [ [[package]] name = "pallet-ethereum" version = "4.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ethereum", "ethereum-types", @@ -6135,7 +6136,7 @@ dependencies = [ [[package]] name = "pallet-evm" version = "6.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "evm", "fp-evm", @@ -6171,6 +6172,7 @@ dependencies = [ "pallet-evm", "pallet-timestamp", "parity-scale-codec", + "paste", "precompile-utils", "scale-info", "serde", @@ -6182,10 +6184,38 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-precompile-balances-erc20" +version = "1.9.0" +dependencies = [ + "derive_more 0.99.17", + "fp-evm", + "frame-support", + "frame-system", + "hex-literal", + "libsecp256k1", + "log", + "num_enum", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "paste", + "precompile-utils", + "scale-info", + "serde", + "sha3 0.8.2", + "slices", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-blake2" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", ] @@ -6193,7 +6223,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-bn128" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "sp-core", @@ -6203,7 +6233,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-dispatch" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "frame-support", @@ -6213,7 +6243,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-ed25519" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "ed25519-dalek", "fp-evm", @@ -6222,7 +6252,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "num", @@ -6231,7 +6261,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "tiny-keccak", @@ -6240,7 +6270,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-simple" version = "2.0.0-dev" -source = "git+https://github.com/parallel-finance/frontier?branch=polkadot-v0.9.26#08df34ea5eae591c79fdf6d62058fa105b85c3a5" +source = "git+https://github.com/parallel-finance/frontier.git?rev=a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b#a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b" dependencies = [ "fp-evm", "ripemd", @@ -7196,6 +7226,7 @@ dependencies = [ "fp-dynamic-fee", "fp-evm", "fp-rpc", + "fp-self-contained", "fp-storage", "frame-benchmarking", "frame-benchmarking-cli", @@ -7207,6 +7238,7 @@ dependencies = [ "kerria-runtime", "log", "orml-oracle-rpc", + "pallet-base-fee", "pallet-ethereum", "pallet-evm", "pallet-loans-rpc", @@ -7631,18 +7663,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2 1.0.43", "quote 1.0.21", @@ -9676,13 +9708,13 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.7" +version = "0.35.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" +checksum = "0d2b64e76b8da597d38473a277e87b91c3747d0cf9922be989e68fc1c046adf9" dependencies = [ "bitflags", "errno", - "io-lifetimes 0.7.2", + "io-lifetimes 0.7.3", "libc", "linux-raw-sys 0.0.46", "windows-sys", @@ -9845,7 +9877,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.26#e8a7d161f39db70cb27fdad6c6e215cf493ebc3b" dependencies = [ "impl-trait-for-tuples", - "memmap2 0.5.6", + "memmap2 0.5.7", "parity-scale-codec", "sc-chain-spec-derive", "sc-network", @@ -10230,7 +10262,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-wasm 0.42.2", - "rustix 0.35.7", + "rustix 0.35.8", "sc-allocator", "sc-executor-common", "sp-runtime-interface", @@ -13109,6 +13141,7 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-balances-erc20", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dispatch", diff --git a/Cargo.toml b/Cargo.toml index c9fad12dd..daa2f9627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ lto = true members = ['node/*', 'pallets/*', 'runtime/*', 'support', 'precompiles/*','integration-tests'] [patch.crates-io] +#orml orml-oracle = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } orml-oracle-rpc = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } orml-oracle-rpc-runtime-api = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } @@ -22,3 +23,25 @@ orml-vesting = { git = 'https://github.com/open-web3-stack/open-r orml-xcm = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', rev = '8c625a5ab43c1c56cdeed5f8d814a891566d4cf8' } +#evm +fc-consensus = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fc-db = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fc-mapping-sync = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fc-rpc = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fc-rpc-core = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-consensus = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-evm = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-rpc = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-storage = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-dynamic-fee = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-ethereum = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +fp-self-contained = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-base-fee = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-blake2 = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-bn128 = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-dispatch = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-ed25519 = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-modexp = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-sha3fips = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } +pallet-evm-precompile-simple = { git = 'https://github.com/parallel-finance/frontier.git', rev = 'a782f9ab37a442c2c6eb0d9d54b1634a24a9e20b' } diff --git a/Makefile b/Makefile index 93e82dee8..42d9226fd 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ clean: cargo clean -p parallel -p vanilla-runtime -p kerria-runtime -p heiko-runtime -p parallel-runtime .PHONY: ci -ci: check lint check-helper check-wasm test integration-test +ci: check check-with-evm lint check-helper check-wasm test integration-test .PHONY: check check: @@ -73,7 +73,7 @@ check: .PHONY: check-with-evm check-with-evm: - SKIP_WASM_BUILD= cargo check --all-targets --features with-evm-runtime --features runtime-benchmarks --features try-runtime + SKIP_WASM_BUILD= cargo check --all-targets --features with-evm-runtime --features runtime-benchmarks --features try-runtime --features testing .PHONY: check-wasm check-wasm: @@ -115,6 +115,9 @@ integration-test-kusama-call: integration-test-sibling-transfer: RUST_LOG="xcm=trace,xcm-executor=trace" SKIP_WASM_BUILD= cargo test -p runtime-integration-tests -- sibling_transfer --nocapture +.PHONY: test-pallet-evm-precompile-balances-erc20 +test-pallet-evm-precompile-balances-erc20: + SKIP_WASM_BUILD= cargo test -p pallet-evm-precompile-balances-erc20 --lib --no-fail-fast -- --nocapture .PHONY: bench bench:build-release-if-not-exists diff --git a/config.json b/config.json index 10556955b..a84f56044 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "relaychain": { "bin": "../polkadot/target/release/polkadot", - "chain": "rococo-local", + "chain": "polkadot-local", "nodes": [ { "name": "alice", @@ -53,22 +53,22 @@ } ], "genesis": { - "runtime": { - "runtime_genesis_config": { - "configuration": { - "config": { - "validation_upgrade_frequency": 10, - "validation_upgrade_delay": 10 - } - } - } - } - } + "runtime": { + "runtime_genesis_config": { + "configuration": { + "config": { + "validation_upgrade_frequency": 10, + "validation_upgrade_delay": 10 + } + } + } + } + } }, "parachains": [ { "bin": "./target/release/parallel", - "chain": "vanilla-dev", + "chain": "kerria-dev", "nodes": [ { "wsPort": 9948, @@ -86,7 +86,8 @@ "0" ] } - ] + ], + "id": 2012 } ], "simpleParachains": [], diff --git a/node/parallel/Cargo.toml b/node/parallel/Cargo.toml index 1279b6887..ede97c380 100644 --- a/node/parallel/Cargo.toml +++ b/node/parallel/Cargo.toml @@ -102,18 +102,20 @@ polkadot-primitives = { git = 'https://github.com/paritytech/polkadot.git', bran polkadot-service = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.26' } # Frontier dependencies -fc-consensus = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fc-db = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fc-mapping-sync = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fc-rpc = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", features = ["rpc_binary_search_estimate"] } -fc-rpc-core = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fp-consensus = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fp-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -fp-rpc = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fp-storage = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -fp-dynamic-fee = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -pallet-ethereum = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } -pallet-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26" } +fc-consensus = { version='2.0.0-dev' } +fc-db = { version='2.0.0-dev' } +fc-mapping-sync = { version='2.0.0-dev' } +fc-rpc = { version='2.0.0-dev', features = ["rpc_binary_search_estimate"] } +fc-rpc-core = { version='1.1.0-dev' } +fp-consensus = { version='2.0.0-dev' } +fp-evm = { version='3.0.0-dev', default-features = false } +fp-storage = { version='2.0.0' } +fp-dynamic-fee = { version='1.0.0' } +fp-rpc = { version='3.0.0-dev'} +fp-self-contained = { version='1.0.0-dev'} +pallet-base-fee = { version='1.0.0'} +pallet-ethereum = { version='4.0.0-dev'} +pallet-evm = { version='6.0.0-dev'} [build-dependencies] diff --git a/node/parallel/src/chain_spec/kerria.rs b/node/parallel/src/chain_spec/kerria.rs index 47e16f885..2f07a8f2b 100644 --- a/node/parallel/src/chain_spec/kerria.rs +++ b/node/parallel/src/chain_spec/kerria.rs @@ -17,8 +17,8 @@ use kerria_runtime::{ CollatorSelectionConfig, CrowdloansAutomatorsMembershipConfig, DemocracyConfig, EVMConfig, GeneralCouncilConfig, GeneralCouncilMembershipConfig, GenesisConfig, LiquidStakingAgentsMembershipConfig, LiquidStakingConfig, OracleMembershipConfig, - ParachainInfoConfig, PolkadotXcmConfig, Precompiles, SessionConfig, SudoConfig, SystemConfig, - TechnicalCommitteeMembershipConfig, VestingConfig, WASM_BINARY, + ParachainInfoConfig, ParallelPrecompilesType, PolkadotXcmConfig, SessionConfig, SudoConfig, + SystemConfig, TechnicalCommitteeMembershipConfig, VestingConfig, WASM_BINARY, }; use primitives::{network::NetworkType, *}; use sc_service::ChainType; @@ -207,7 +207,7 @@ fn kerria_genesis( evm: EVMConfig { // We need _some_ code inserted at the precompile address so that // the evm will actually call the address. - accounts: Precompiles::used_addresses() + accounts: ParallelPrecompilesType::used_addresses() .map(|addr| { ( addr, diff --git a/node/parallel/src/chain_spec/vanilla.rs b/node/parallel/src/chain_spec/vanilla.rs index 00ccd2665..b2c00cb65 100644 --- a/node/parallel/src/chain_spec/vanilla.rs +++ b/node/parallel/src/chain_spec/vanilla.rs @@ -23,8 +23,8 @@ use vanilla_runtime::{ CollatorSelectionConfig, CrowdloansAutomatorsMembershipConfig, DemocracyConfig, EVMConfig, GeneralCouncilConfig, GeneralCouncilMembershipConfig, GenesisConfig, LiquidStakingAgentsMembershipConfig, LiquidStakingConfig, OracleMembershipConfig, - ParachainInfoConfig, PolkadotXcmConfig, Precompiles, SessionConfig, SudoConfig, SystemConfig, - TechnicalCommitteeMembershipConfig, VestingConfig, WASM_BINARY, + ParachainInfoConfig, ParallelPrecompilesType, PolkadotXcmConfig, SessionConfig, SudoConfig, + SystemConfig, TechnicalCommitteeMembershipConfig, VestingConfig, WASM_BINARY, }; use crate::chain_spec::{ @@ -276,7 +276,7 @@ fn vanilla_genesis( evm: EVMConfig { // We need _some_ code inserted at the precompile address so that // the evm will actually call the address. - accounts: Precompiles::used_addresses() + accounts: ParallelPrecompilesType::used_addresses() .map(|addr| { ( addr, diff --git a/precompiles/assets-erc20/Cargo.toml b/precompiles/assets-erc20/Cargo.toml index 33b7ec2c5..efe5829be 100644 --- a/precompiles/assets-erc20/Cargo.toml +++ b/precompiles/assets-erc20/Cargo.toml @@ -7,6 +7,7 @@ version = '1.9.0' [dependencies] log = "0.4.16" num_enum = { version = "0.5.3", default-features = false } +paste = "1.0.6" slices = "0.2.0" precompile-utils = { path = "../utils", default-features = false } @@ -17,14 +18,16 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "pol frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +scale-info = { version = "2.1.0", default-features = false, features = [ "derive" ] } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } # Frontier -fp-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } +fp-evm = { version='3.0.0-dev', default-features = false } +pallet-evm = { version='6.0.0-dev', default-features = false } [dev-dependencies] derive_more = { version = "0.99" } @@ -34,7 +37,6 @@ sha3 = "0.10.1" precompile-utils = { path = "../utils", features = ["testing"] } codec = { package = "parity-scale-codec", version = "3.0.0", features = ["max-encoded-len"] } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } scale-info = { version = "2.1.0", default-features = false, features = ["derive"] } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } @@ -48,6 +50,7 @@ std = [ "pallet-assets/std", "pallet-evm/std", "pallet-balances/std", + "pallet-timestamp/std", "precompile-utils/std", "sp-core/std", "sp-io/std", diff --git a/precompiles/assets-erc20/src/eip2612.rs b/precompiles/assets-erc20/src/eip2612.rs new file mode 100644 index 000000000..cdd015ecb --- /dev/null +++ b/precompiles/assets-erc20/src/eip2612.rs @@ -0,0 +1,273 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::{ + ensure, + storage::types::{StorageDoubleMap, ValueQuery}, + traits::StorageInstance, + Blake2_128Concat, +}; +use pallet_assets::pallet::{ + Instance1, Instance10, Instance11, Instance12, Instance13, Instance14, Instance15, Instance16, + Instance2, Instance3, Instance4, Instance5, Instance6, Instance7, Instance8, Instance9, +}; +use precompile_utils::revert; +use scale_info::prelude::string::ToString; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::traits::Get; +use sp_std::vec::Vec; + +/// EIP2612 permit typehash. +pub const PERMIT_TYPEHASH: [u8; 32] = keccak256!( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" +); + +/// EIP2612 permit domain used to compute an individualized domain separator. +const PERMIT_DOMAIN: [u8; 32] = keccak256!( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" +); + +/// Associates pallet Instance to a prefix used for the Nonces storage. +/// This trait is implemented for () and the 16 substrate Instance. +pub trait InstanceToPrefix { + /// Prefix used for the Approves storage. + type NoncesPrefix: StorageInstance; +} + +// We use a macro to implement the trait for () and the 16 substrate Instance. +macro_rules! impl_prefix { + ($instance:ident, $name:literal) => { + // Using `paste!` we generate a dedicated module to avoid collisions + // between each instance `Nonces` struct. + paste::paste! { + mod [<_impl_prefix_ $instance:snake>] { + use super::*; + + pub struct Nonces; + + impl StorageInstance for Nonces { + const STORAGE_PREFIX: &'static str = "Nonces"; + + fn pallet_prefix() -> &'static str { + $name + } + } + + impl InstanceToPrefix for $instance { + type NoncesPrefix = Nonces; + } + } + } + }; +} + +// Since the macro expect a `ident` to be used with `paste!` we cannot provide `()` directly. +type Instance0 = (); + +impl_prefix!(Instance0, "Erc20Instance0Assets"); +impl_prefix!(Instance1, "Erc20Instance1Assets"); +impl_prefix!(Instance2, "Erc20Instance2Assets"); +impl_prefix!(Instance3, "Erc20Instance3Assets"); +impl_prefix!(Instance4, "Erc20Instance4Assets"); +impl_prefix!(Instance5, "Erc20Instance5Assets"); +impl_prefix!(Instance6, "Erc20Instance6Assets"); +impl_prefix!(Instance7, "Erc20Instance7Assets"); +impl_prefix!(Instance8, "Erc20Instance8Assets"); +impl_prefix!(Instance9, "Erc20Instance9Assets"); +impl_prefix!(Instance10, "Erc20Instance10Assets"); +impl_prefix!(Instance11, "Erc20Instance11Assets"); +impl_prefix!(Instance12, "Erc20Instance12Assets"); +impl_prefix!(Instance13, "Erc20Instance13Assets"); +impl_prefix!(Instance14, "Erc20Instance14Assets"); +impl_prefix!(Instance15, "Erc20Instance15Assets"); +impl_prefix!(Instance16, "Erc20Instance16Assets"); + +/// Storage type used to store EIP2612 nonces. +pub type NoncesStorage = StorageDoubleMap< + ::NoncesPrefix, + // Asset contract address + Blake2_128Concat, + H160, + // Owner + Blake2_128Concat, + H160, + // Nonce + U256, + ValueQuery, +>; + +pub struct Eip2612(PhantomData<(Runtime, Instance)>); + +impl Eip2612 +where + Instance: InstanceToPrefix + 'static, + Runtime: pallet_assets::Config + + pallet_evm::Config + + frame_system::Config + + pallet_timestamp::Config, + Runtime::Call: Dispatchable + GetDispatchInfo, + Runtime::Call: From>, + ::Origin: From>, + BalanceOf: TryFrom + Into + EvmData, + // Runtime: AccountIdAssetIdConversion>, + Runtime: AddressToAssetId>, + <::Call as Dispatchable>::Origin: OriginTrait, + ::Moment: Into, + AssetIdOf: Display, +{ + fn compute_domain_separator(address: H160, asset_id: AssetIdOf) -> [u8; 32] { + let asset_name = pallet_assets::Pallet::::name(asset_id); + + let name = if asset_name.is_empty() { + let mut name = b"Unnamed XC20 #".to_vec(); + name.extend_from_slice(asset_id.to_string().as_bytes()); + name + } else { + asset_name + }; + + let name: H256 = keccak_256(&name).into(); + let version: H256 = keccak256!("1").into(); + let chain_id: U256 = Runtime::ChainId::get().into(); + + let domain_separator_inner = EvmDataWriter::new() + .write(H256::from(PERMIT_DOMAIN)) + .write(name) + .write(version) + .write(chain_id) + .write(Address(address)) + .build(); + + keccak_256(&domain_separator_inner) + } + + pub fn generate_permit( + address: H160, + asset_id: AssetIdOf, + owner: H160, + spender: H160, + value: U256, + nonce: U256, + deadline: U256, + ) -> [u8; 32] { + let domain_separator = Self::compute_domain_separator(address, asset_id); + + let permit_content = EvmDataWriter::new() + .write(H256::from(PERMIT_TYPEHASH)) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(nonce) + .write(deadline) + .build(); + let permit_content = keccak_256(&permit_content); + + let mut pre_digest = Vec::with_capacity(2 + 32 + 32); + pre_digest.extend_from_slice(b"\x19\x01"); + pre_digest.extend_from_slice(&domain_separator); + pre_digest.extend_from_slice(&permit_content); + keccak_256(&pre_digest) + } + + // Translated from + // https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L81 + pub(crate) fn permit( + asset_id: AssetIdOf, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let mut input = handle.read_input()?; + let owner: H160 = input.read::
()?.into(); + let spender: H160 = input.read::
()?.into(); + let value: U256 = input.read()?; + let deadline: U256 = input.read()?; + let v: u8 = input.read()?; + let r: H256 = input.read()?; + let s: H256 = input.read()?; + + let address = handle.code_address(); + + // pallet_timestamp is in ms while Ethereum use second timestamps. + let timestamp: U256 = (pallet_timestamp::Pallet::::get()).into() / 1000; + + ensure!(deadline >= timestamp, revert("permit expired")); + + let nonce = NoncesStorage::::get(address, owner); + + let permit = + Self::generate_permit(address, asset_id, owner, spender, value, nonce, deadline); + + let mut sig = [0u8; 65]; + sig[0..32].copy_from_slice(r.as_bytes()); + sig[32..64].copy_from_slice(s.as_bytes()); + sig[64] = v; + + let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit) + .map_err(|_| revert("invalid permit"))?; + let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice())); + + ensure!( + signer != H160::zero() && signer == owner, + revert("invalid permit") + ); + + NoncesStorage::::insert(address, owner, nonce + U256::one()); + + Erc20AssetsPrecompileSet::::approve_inner( + asset_id, handle, owner, spender, value, + )?; + + LogsBuilder::new(handle.context().address) + .log3( + SELECTOR_LOG_APPROVAL, + owner, + spender, + EvmDataWriter::new().write(value).build(), + ) + .record(handle)?; + + Ok(succeed([])) + } + + pub(crate) fn nonces( + _asset_id: AssetIdOf, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let mut input = handle.read_input()?; + let owner: H160 = input.read::
()?.into(); + + let nonce = NoncesStorage::::get(handle.code_address(), owner); + + Ok(succeed(EvmDataWriter::new().write(nonce).build())) + } + + pub(crate) fn domain_separator( + asset_id: AssetIdOf, + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let domain_separator: H256 = + Self::compute_domain_separator(handle.code_address(), asset_id).into(); + + Ok(succeed( + EvmDataWriter::new().write(domain_separator).build(), + )) + } +} diff --git a/precompiles/assets-erc20/src/lib.rs b/precompiles/assets-erc20/src/lib.rs index dae339051..6d2713c6d 100644 --- a/precompiles/assets-erc20/src/lib.rs +++ b/precompiles/assets-erc20/src/lib.rs @@ -31,12 +31,15 @@ use precompile_utils::{ }; use sp_runtime::traits::Bounded; +use core::fmt::Display; use sp_core::{H160, U256}; use sp_std::{ convert::{TryFrom, TryInto}, marker::PhantomData, }; +mod eip2612; + #[cfg(test)] mod mock; #[cfg(test)] @@ -69,6 +72,10 @@ pub enum Action { MinimumBalance = "minimumBalance()", Mint = "mint(address,uint256)", Burn = "burn(address,uint256)", + // EIP 2612 + Eip2612Permit = "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", + Eip2612Nonces = "nonces(address)", + Eip2612DomainSeparator = "DOMAIN_SEPARATOR()", } /// This trait ensure we can convert EVM address to AssetIds @@ -109,7 +116,7 @@ impl Default for Erc20AssetsPrecompileSet impl PrecompileSet for Erc20AssetsPrecompileSet where - Instance: 'static, + Instance: eip2612::InstanceToPrefix + 'static, Runtime: pallet_assets::Config + pallet_evm::Config + frame_system::Config, Runtime::Call: Dispatchable + GetDispatchInfo, Runtime::Call: From>, @@ -117,6 +124,8 @@ where BalanceOf: TryFrom + Into + EvmData, Runtime: AddressToAssetId>, <::Call as Dispatchable>::Origin: OriginTrait, + ::Moment: Into, + AssetIdOf: Display, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option> { let address = handle.code_address(); @@ -157,6 +166,18 @@ where Action::MinimumBalance => Self::minimum_balance(asset_id, handle), Action::Mint => Self::mint(asset_id, handle), Action::Burn => Self::burn(asset_id, handle), + // EIP2612 + Action::Eip2612Permit => { + eip2612::Eip2612::::permit(asset_id, handle) + } + Action::Eip2612Nonces => { + eip2612::Eip2612::::nonces(asset_id, handle) + } + Action::Eip2612DomainSeparator => { + eip2612::Eip2612::::domain_separator( + asset_id, handle, + ) + } } }; return Some(result); @@ -176,7 +197,7 @@ where impl Erc20AssetsPrecompileSet where - Instance: 'static, + Instance: eip2612::InstanceToPrefix + 'static, Runtime: pallet_assets::Config + pallet_evm::Config + frame_system::Config, Runtime::Call: Dispatchable + GetDispatchInfo, Runtime::Call: From>, @@ -254,40 +275,7 @@ where let spender: H160 = input.read::
()?.into(); let amount: U256 = input.read()?; - { - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); - // Amount saturate if too high. - let amount: BalanceOf = - amount.try_into().unwrap_or_else(|_| Bounded::max_value()); - - // Allowance read - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - - // If previous approval exists, we need to clean it - if pallet_assets::Pallet::::allowance(asset_id, &origin, &spender) - != 0u32.into() - { - RuntimeHelper::::try_dispatch( - handle, - Some(origin.clone()).into(), - pallet_assets::Call::::cancel_approval { - id: asset_id, - delegate: Runtime::Lookup::unlookup(spender.clone()), - }, - )?; - } - // Dispatch call (if enough gas). - RuntimeHelper::::try_dispatch( - handle, - Some(origin).into(), - pallet_assets::Call::::approve_transfer { - id: asset_id, - delegate: Runtime::Lookup::unlookup(spender), - amount, - }, - )?; - } + Self::approve_inner(asset_id, handle, handle.context().caller, spender, amount)?; LogsBuilder::new(handle.context().address) .log3( @@ -301,6 +289,47 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + fn approve_inner( + asset_id: AssetIdOf, + handle: &mut impl PrecompileHandle, + owner: H160, + spender: H160, + value: U256, + ) -> EvmResult { + let owner = Runtime::AddressMapping::into_account_id(owner); + let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); + // Amount saturate if too high. + let amount: BalanceOf = + value.try_into().unwrap_or_else(|_| Bounded::max_value()); + + // Allowance read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + // If previous approval exists, we need to clean it + if pallet_assets::Pallet::::allowance(asset_id, &owner, &spender) + != 0u32.into() + { + RuntimeHelper::::try_dispatch( + handle, + Some(owner.clone()).into(), + pallet_assets::Call::::cancel_approval { + id: asset_id, + delegate: Runtime::Lookup::unlookup(spender.clone()), + }, + )?; + } + // Dispatch call (if enough gas). + RuntimeHelper::::try_dispatch( + handle, + Some(owner).into(), + pallet_assets::Call::::approve_transfer { + id: asset_id, + delegate: Runtime::Lookup::unlookup(spender), + amount, + }, + ) + } + fn transfer( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, diff --git a/precompiles/balances-erc20/Cargo.toml b/precompiles/balances-erc20/Cargo.toml new file mode 100644 index 000000000..06fdace72 --- /dev/null +++ b/precompiles/balances-erc20/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "pallet-evm-precompile-balances-erc20" +authors = [ "Parallel Team" ] +description = "A Precompile to expose a Balances pallet through an ERC20-compliant interface." +edition = "2021" +version = "1.9.0" + +[dependencies] +log = "0.4" +num_enum = { version = "0.5.3", default-features = false } +paste = "1.0.6" +slices = "0.2.0" + +# Moonbeam +precompile-utils = { path = "../utils", default-features = false } + +# Substrate +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "max-encoded-len" ] } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } + +# Frontier +fp-evm = { version='3.0.0-dev', default-features = false } +pallet-evm = { version='6.0.0-dev', default-features = false } + +[dev-dependencies] +derive_more = { version = "0.99" } +hex-literal = "0.3.4" +libsecp256k1 = "0.7" +serde = { version = "1.0.100" } +sha3 = "0.8" + +# Moonbeam +precompile-utils = { path = "../utils", features = [ "testing" ] } + +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } +scale-info = { version = "2.0", default-features = false, features = [ "derive" ] } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } + +[features] +default = [ "std" ] +std = [ + "codec/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-evm/std", + "precompile-utils/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", +] diff --git a/precompiles/balances-erc20/ERC20.sol b/precompiles/balances-erc20/ERC20.sol new file mode 100644 index 000000000..7e598ad35 --- /dev/null +++ b/precompiles/balances-erc20/ERC20.sol @@ -0,0 +1,116 @@ +pragma solidity ^0.8.0; + +/// @title ERC20 interface +/// @dev see https://github.com/ethereum/EIPs/issues/20 +/// @dev copied from https://github.com/OpenZeppelin/openzeppelin-contracts +/// @custom:address 0x0000000000000000000000000000000000000802 +interface IERC20 { + /// @dev Returns the name of the token. + /// @custom:selector 06fdde03 + function name() external view returns (string memory); + + /// @dev Returns the symbol of the token. + /// @custom:selector 95d89b41 + function symbol() external view returns (string memory); + + /// @dev Returns the decimals places of the token. + /// @custom:selector 313ce567 + function decimals() external view returns (uint8); + + /// @dev Total number of tokens in existence + /// @custom:selector 18160ddd + function totalSupply() external view returns (uint256); + + /// @dev Gets the balance of the specified address. + /// @custom:selector 70a08231 + /// @param owner The address to query the balance of. + /// @return An uint256 representing the amount owned by the passed address. + function balanceOf(address owner) external view returns (uint256); + + /// @dev Function to check the amount of tokens that an owner allowed to a spender. + /// @custom:selector dd62ed3e + /// @param owner address The address which owns the funds. + /// @param spender address The address which will spend the funds. + /// @return A uint256 specifying the amount of tokens still available for the spender. + function allowance(address owner, address spender) + external + view + returns (uint256); + + /// @dev Transfer token for a specified address + /// @custom:selector a9059cbb + /// @param to The address to transfer to. + /// @param value The amount to be transferred. + /// @return true if the transfer was successful, revert otherwise. + function transfer(address to, uint256 value) external returns (bool); + + /// @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + /// Beware that changing an allowance with this method brings the risk that someone may use both the old + /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + /// @custom:selector 095ea7b3 + /// @param spender The address which will spend the funds. + /// @param value The amount of tokens to be spent. + /// @return true, this cannot fail + function approve(address spender, uint256 value) external returns (bool); + + /// @dev Transfer tokens from one address to another + /// @custom:selector 23b872dd + /// @param from address The address which you want to send tokens from + /// @param to address The address which you want to transfer to + /// @param value uint256 the amount of tokens to be transferred + /// @return true if the transfer was successful, revert otherwise. + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + /// @dev Event emitted when a transfer has been performed. + /// @custom:selector ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + /// @param from address The address sending the tokens + /// @param to address The address receiving the tokens. + /// @param value uint256 The amount of tokens transferred. + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @dev Event emitted when an approval has been registered. + /// @custom:selector 8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 + /// @param owner address Owner of the tokens. + /// @param spender address Allowed spender. + /// @param value uint256 Amount of tokens approved. + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +/// @title Native currency wrapper interface. +/// @dev Allow compatibility with dApps expecting this precompile to be +/// a WETH-like contract. +/// Moonbase address : 0x0000000000000000000000000000000000000802 +interface WrappedNativeCurrency { + /// @dev Provide compatibility for contracts that expect wETH design. + /// Returns funds to sender as this precompile tokens and the native tokens are the same. + /// @custom:selector d0e30db0 + function deposit() external payable; + + /// @dev Provide compatibility for contracts that expect wETH design. + /// Does nothing. + /// @custom:selector 2e1a7d4d + /// @param value uint256 The amount to withdraw/unwrap. + function withdraw(uint256 value) external; + + /// @dev Event emitted when deposit() has been called. + /// @custom:selector e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c + /// @param owner address Owner of the tokens + /// @param value uint256 The amount of tokens "wrapped". + event Deposit(address indexed owner, uint256 value); + + /// @dev Event emitted when withdraw(uint256) has been called. + /// @custom:selector 7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65 + /// @param owner address Owner of the tokens + /// @param value uint256 The amount of tokens "unwrapped". + event Withdrawal(address indexed owner, uint256 value); +} diff --git a/precompiles/balances-erc20/Permit.sol b/precompiles/balances-erc20/Permit.sol new file mode 100644 index 000000000..f4d8aa10e --- /dev/null +++ b/precompiles/balances-erc20/Permit.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.0; + +/// @title Extension of the ERC20 interface that allows users to +/// sign permit messages to interact with contracts without needing to +/// make a first approve transaction. +interface Permit { + /// @dev Consumes an approval permit. + /// Anyone can call this function for a permit. + /// @custom:selector d505accf + /// @param owner Owner of the tokens issuing the permit + /// @param spender Address whose allowance will be increased. + /// @param value Allowed value. + /// @param deadline Timestamp after which the permit will no longer be valid. + /// @param v V component of the signature. + /// @param r R component of the signature. + /// @param s S component of the signature. + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /// @dev Returns the current nonce for given owner. + /// A permit must have this nonce to be consumed, which will + /// increase the nonce by one. + /// @custom:selector 7ecebe00 + function nonces(address owner) external view returns (uint256); + + /// @dev Returns the EIP712 domain separator. It is used to avoid replay + /// attacks across assets or other similar EIP712 message structures. + /// @custom:selector 3644e515 + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/precompiles/balances-erc20/src/eip2612.rs b/precompiles/balances-erc20/src/eip2612.rs new file mode 100644 index 000000000..a0671b974 --- /dev/null +++ b/precompiles/balances-erc20/src/eip2612.rs @@ -0,0 +1,180 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::{ensure, traits::Get}; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_std::vec::Vec; + +/// EIP2612 permit typehash. +pub const PERMIT_TYPEHASH: [u8; 32] = keccak256!( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" +); + +/// EIP2612 permit domain used to compute an individualized domain separator. +const PERMIT_DOMAIN: [u8; 32] = keccak256!( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" +); + +pub struct Eip2612(PhantomData<(Runtime, Metadata, Instance)>); + +impl Eip2612 +where + Metadata: Erc20Metadata, + Instance: InstanceToPrefix + 'static, + Runtime: pallet_balances::Config + pallet_evm::Config + pallet_timestamp::Config, + Runtime::Call: Dispatchable + GetDispatchInfo, + Runtime::Call: From>, + ::Origin: From>, + BalanceOf: TryFrom + Into, + ::Moment: Into, +{ + fn compute_domain_separator(address: H160) -> [u8; 32] { + let name: H256 = keccak_256(Metadata::name().as_bytes()).into(); + + let version: H256 = keccak256!("1").into(); + + let chain_id: U256 = Runtime::ChainId::get().into(); + + let domain_separator_inner = EvmDataWriter::new() + .write(H256::from(PERMIT_DOMAIN)) + .write(name) + .write(version) + .write(chain_id) + .write(Address(address)) + .build(); + + keccak_256(&domain_separator_inner) + } + + pub fn generate_permit( + address: H160, + owner: H160, + spender: H160, + value: U256, + nonce: U256, + deadline: U256, + ) -> [u8; 32] { + let domain_separator = Self::compute_domain_separator(address); + + let permit_content = EvmDataWriter::new() + .write(H256::from(PERMIT_TYPEHASH)) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(nonce) + .write(deadline) + .build(); + let permit_content = keccak_256(&permit_content); + + let mut pre_digest = Vec::with_capacity(2 + 32 + 32); + pre_digest.extend_from_slice(b"\x19\x01"); + pre_digest.extend_from_slice(&domain_separator); + pre_digest.extend_from_slice(&permit_content); + keccak_256(&pre_digest) + } + + // Translated from + // https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L81 + pub(crate) fn permit(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let mut input = EvmDataReader::new_skip_selector(handle.input())?; + let owner: H160 = input.read::
()?.into(); + let spender: H160 = input.read::
()?.into(); + let value: U256 = input.read()?; + let deadline: U256 = input.read()?; + let v: u8 = input.read()?; + let r: H256 = input.read()?; + let s: H256 = input.read()?; + + // pallet_timestamp is in ms while Ethereum use second timestamps. + let timestamp: U256 = (pallet_timestamp::Pallet::::get()).into() / 1000; + + ensure!(deadline >= timestamp, revert("permit expired")); + + let nonce = NoncesStorage::::get(owner); + + let permit = Self::generate_permit( + handle.context().address, + owner, + spender, + value, + nonce, + deadline, + ); + + let mut sig = [0u8; 65]; + sig[0..32].copy_from_slice(r.as_bytes()); + sig[32..64].copy_from_slice(s.as_bytes()); + sig[64] = v; + + let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit) + .map_err(|_| revert("invalid permit"))?; + let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice())); + + ensure!( + signer != H160::zero() && signer == owner, + revert("invalid permit") + ); + + NoncesStorage::::insert(owner, nonce + U256::one()); + + { + let amount = + Erc20BalancesPrecompile::::u256_to_amount(value) + .unwrap_or_else(|_| Bounded::max_value()); + + let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner); + let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); + ApprovesStorage::::insert(owner, spender, amount); + } + + LogsBuilder::new(handle.context().address) + .log3( + SELECTOR_LOG_APPROVAL, + owner, + spender, + EvmDataWriter::new().write(value).build(), + ) + .record(handle)?; + + Ok(succeed([])) + } + + pub(crate) fn nonces(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let mut input = EvmDataReader::new_skip_selector(handle.input())?; + let owner: H160 = input.read::
()?.into(); + + let nonce = NoncesStorage::::get(owner); + + Ok(succeed(EvmDataWriter::new().write(nonce).build())) + } + + pub(crate) fn domain_separator( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let domain_separator: H256 = + Self::compute_domain_separator(handle.context().address).into(); + + Ok(succeed( + EvmDataWriter::new().write(domain_separator).build(), + )) + } +} diff --git a/precompiles/balances-erc20/src/lib.rs b/precompiles/balances-erc20/src/lib.rs new file mode 100644 index 000000000..2873279b7 --- /dev/null +++ b/precompiles/balances-erc20/src/lib.rs @@ -0,0 +1,583 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +use fp_evm::{Precompile, PrecompileHandle, PrecompileOutput}; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + sp_runtime::traits::{Bounded, CheckedSub, StaticLookup}, + storage::types::{StorageDoubleMap, StorageMap, ValueQuery}, + traits::StorageInstance, + Blake2_128Concat, +}; +use pallet_balances::pallet::{ + Instance1, Instance10, Instance11, Instance12, Instance13, Instance14, Instance15, Instance16, + Instance2, Instance3, Instance4, Instance5, Instance6, Instance7, Instance8, Instance9, +}; +use pallet_evm::AddressMapping; +use sp_core::{H160, U256}; +use sp_std::{ + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; + +use precompile_utils::{ + generate_function_selector, keccak256, revert, succeed, Address, Bytes, EvmDataReader, + EvmDataWriter, EvmResult, FunctionModifier, LogExt, LogsBuilder, PrecompileHandleExt, + RuntimeHelper, +}; + +mod eip2612; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +/// Solidity selector of the Transfer log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address,uint256)"); + +/// Solidity selector of the Approval log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)"); + +/// Solidity selector of the Deposit log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_DEPOSIT: [u8; 32] = keccak256!("Deposit(address,uint256)"); + +/// Solidity selector of the Withdraw log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_WITHDRAWAL: [u8; 32] = keccak256!("Withdrawal(address,uint256)"); + +/// Associates pallet Instance to a prefix used for the Approves storage. +/// This trait is implemented for () and the 16 substrate Instance. +pub trait InstanceToPrefix { + /// Prefix used for the Approves storage. + type ApprovesPrefix: StorageInstance; + + /// Prefix used for the Approves storage. + type NoncesPrefix: StorageInstance; +} + +// We use a macro to implement the trait for () and the 16 substrate Instance. +macro_rules! impl_prefix { + ($instance:ident, $name:literal) => { + // Using `paste!` we generate a dedicated module to avoid collisions + // between each instance `Approves` struct. + paste::paste! { + mod [<_impl_prefix_ $instance:snake>] { + use super::*; + + pub struct Approves; + + impl StorageInstance for Approves { + const STORAGE_PREFIX: &'static str = "Approves"; + + fn pallet_prefix() -> &'static str { + $name + } + } + + pub struct Nonces; + + impl StorageInstance for Nonces { + const STORAGE_PREFIX: &'static str = "Nonces"; + + fn pallet_prefix() -> &'static str { + $name + } + } + + impl InstanceToPrefix for $instance { + type ApprovesPrefix = Approves; + type NoncesPrefix = Nonces; + } + } + } + }; +} + +// Since the macro expect a `ident` to be used with `paste!` we cannot provide `()` directly. +type Instance0 = (); + +impl_prefix!(Instance0, "Erc20Instance0Balances"); +impl_prefix!(Instance1, "Erc20Instance1Balances"); +impl_prefix!(Instance2, "Erc20Instance2Balances"); +impl_prefix!(Instance3, "Erc20Instance3Balances"); +impl_prefix!(Instance4, "Erc20Instance4Balances"); +impl_prefix!(Instance5, "Erc20Instance5Balances"); +impl_prefix!(Instance6, "Erc20Instance6Balances"); +impl_prefix!(Instance7, "Erc20Instance7Balances"); +impl_prefix!(Instance8, "Erc20Instance8Balances"); +impl_prefix!(Instance9, "Erc20Instance9Balances"); +impl_prefix!(Instance10, "Erc20Instance10Balances"); +impl_prefix!(Instance11, "Erc20Instance11Balances"); +impl_prefix!(Instance12, "Erc20Instance12Balances"); +impl_prefix!(Instance13, "Erc20Instance13Balances"); +impl_prefix!(Instance14, "Erc20Instance14Balances"); +impl_prefix!(Instance15, "Erc20Instance15Balances"); +impl_prefix!(Instance16, "Erc20Instance16Balances"); + +/// Alias for the Balance type for the provided Runtime and Instance. +pub type BalanceOf = + >::Balance; + +/// Storage type used to store approvals, since `pallet_balances` doesn't +/// handle this behavior. +/// (Owner => Allowed => Amount) +pub type ApprovesStorage = StorageDoubleMap< + ::ApprovesPrefix, + Blake2_128Concat, + ::AccountId, + Blake2_128Concat, + ::AccountId, + BalanceOf, +>; + +/// Storage type used to store EIP2612 nonces. +pub type NoncesStorage = StorageMap< + ::NoncesPrefix, + // Owner + Blake2_128Concat, + H160, + // Nonce + U256, + ValueQuery, +>; + +#[generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + TotalSupply = "totalSupply()", + BalanceOf = "balanceOf(address)", + Allowance = "allowance(address,address)", + Transfer = "transfer(address,uint256)", + Approve = "approve(address,uint256)", + TransferFrom = "transferFrom(address,address,uint256)", + Name = "name()", + Symbol = "symbol()", + Decimals = "decimals()", + Deposit = "deposit()", + Withdraw = "withdraw(uint256)", + // EIP 2612 + Eip2612Permit = "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", + Eip2612Nonces = "nonces(address)", + Eip2612DomainSeparator = "DOMAIN_SEPARATOR()", +} + +/// Metadata of an ERC20 token. +pub trait Erc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str; + + /// Returns the symbol of the token. + fn symbol() -> &'static str; + + /// Returns the decimals places of the token. + fn decimals() -> u8; + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool; +} + +/// Precompile exposing a pallet_balance as an ERC20. +/// Multiple precompiles can support instances of pallet_balance. +/// The precompile uses an additional storage to store approvals. +pub struct Erc20BalancesPrecompile( + PhantomData<(Runtime, Metadata, Instance)>, +); + +impl Default + for Erc20BalancesPrecompile +{ + fn default() -> Self { + Self(PhantomData) + } +} + +impl Erc20BalancesPrecompile +where + Metadata: Erc20Metadata, + Instance: InstanceToPrefix + 'static, + Runtime: pallet_balances::Config + pallet_evm::Config + pallet_timestamp::Config, + Runtime::Call: Dispatchable + GetDispatchInfo, + Runtime::Call: From>, + ::Origin: From>, + BalanceOf: TryFrom + Into, + ::Moment: Into, +{ + pub fn new() -> Self { + Self(PhantomData) + } +} + +impl Precompile + for Erc20BalancesPrecompile +where + Metadata: Erc20Metadata, + Instance: InstanceToPrefix + 'static, + Runtime: pallet_balances::Config + pallet_evm::Config + pallet_timestamp::Config, + Runtime::Call: Dispatchable + GetDispatchInfo, + Runtime::Call: From>, + ::Origin: From>, + BalanceOf: TryFrom + Into, + ::Moment: Into, +{ + fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { + let selector = handle.read_selector().unwrap_or(Action::Deposit); + + handle + .check_function_modifier(match selector { + Action::Approve | Action::Transfer | Action::TransferFrom | Action::Withdraw => { + FunctionModifier::NonPayable + } + Action::Deposit => FunctionModifier::Payable, + _ => FunctionModifier::View, + }) + .ok(); + + match selector { + Action::TotalSupply => Self::total_supply(handle), + Action::BalanceOf => Self::balance_of(handle), + Action::Allowance => Self::allowance(handle), + Action::Approve => Self::approve(handle), + Action::Transfer => Self::transfer(handle), + Action::TransferFrom => Self::transfer_from(handle), + Action::Name => Self::name(), + Action::Symbol => Self::symbol(), + Action::Decimals => Self::decimals(), + Action::Deposit => Self::deposit(handle), + Action::Withdraw => Self::withdraw(handle), + Action::Eip2612Permit => { + eip2612::Eip2612::::permit(handle) + } + Action::Eip2612Nonces => { + eip2612::Eip2612::::nonces(handle) + } + Action::Eip2612DomainSeparator => { + eip2612::Eip2612::::domain_separator(handle) + } + } + } +} + +impl Erc20BalancesPrecompile +where + Metadata: Erc20Metadata, + Instance: InstanceToPrefix + 'static, + Runtime: pallet_balances::Config + pallet_evm::Config + pallet_timestamp::Config, + Runtime::Call: Dispatchable + GetDispatchInfo, + Runtime::Call: From>, + ::Origin: From>, + BalanceOf: TryFrom + Into, + ::Moment: Into, +{ + fn total_supply(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + // Parse input. + let input = handle.read_input()?; + input.expect_arguments(0)?; + + // Fetch info. + let amount: U256 = pallet_balances::Pallet::::total_issuance().into(); + + // Build output. + Ok(succeed(EvmDataWriter::new().write(amount).build())) + } + + fn balance_of(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + // Read input. + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + let owner: H160 = input.read::
()?.into(); + + // Fetch info. + let amount: U256 = { + let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner); + pallet_balances::Pallet::::usable_balance(&owner).into() + }; + + // Build output. + Ok(succeed(EvmDataWriter::new().write(amount).build())) + } + + fn allowance(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + // Read input. + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let owner: H160 = input.read::
()?.into(); + let spender: H160 = input.read::
()?.into(); + + // Fetch info. + let amount: U256 = { + let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner); + let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); + + ApprovesStorage::::get(owner, spender) + .unwrap_or_default() + .into() + }; + + // Build output. + Ok(succeed(EvmDataWriter::new().write(amount).build())) + } + + fn approve(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + handle.record_log_costs_manual(3, 32)?; + + // Parse input. + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let spender: H160 = input.read::
()?.into(); + let amount: U256 = input.read()?; + + // Write into storage. + { + let caller: Runtime::AccountId = + Runtime::AddressMapping::into_account_id(handle.context().caller); + let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); + // Amount saturate if too high. + let amount = Self::u256_to_amount(amount).unwrap_or_else(|_| Bounded::max_value()); + + ApprovesStorage::::insert(caller, spender, amount); + } + + LogsBuilder::new(handle.context().address) + .log3( + SELECTOR_LOG_APPROVAL, + handle.context().caller, + spender, + EvmDataWriter::new().write(amount).build(), + ) + .record(handle)?; + + // Build output. + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_log_costs_manual(3, 32)?; + + // Parse input. + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let to: H160 = input.read::
()?.into(); + 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( + handle, + Some(origin).into(), + pallet_balances::Call::::transfer { + dest: Runtime::Lookup::unlookup(to), + value: amount, + }, + )?; + } + + LogsBuilder::new(handle.context().address) + .log3( + SELECTOR_LOG_TRANSFER, + handle.context().caller, + to, + EvmDataWriter::new().write(amount).build(), + ) + .record(handle)?; + + // Build output. + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_from(handle: &mut impl PrecompileHandle) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + handle.record_log_costs_manual(3, 32)?; + + // Parse input. + let mut input = handle.read_input()?; + input.expect_arguments(3)?; + let from: H160 = input.read::
()?.into(); + let to: H160 = input.read::
()?.into(); + 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. + if caller != from { + ApprovesStorage::::mutate(from.clone(), caller, |entry| { + // Get current value, exit if None. + let value = entry.ok_or(revert("spender not allowed"))?; + + // Remove "amount" from allowed, exit if underflow. + let new_value = value + .checked_sub(&amount) + .ok_or_else(|| revert("trying to spend more than allowed"))?; + + // Update value. + *entry = Some(new_value); + + EvmResult::Ok(()) + })?; + } + + // Build call with origin. Here origin is the "from"/owner field. + // Dispatch call (if enough gas). + RuntimeHelper::::try_dispatch( + handle, + Some(from).into(), + pallet_balances::Call::::transfer { + dest: Runtime::Lookup::unlookup(to), + value: amount, + }, + )?; + } + + LogsBuilder::new(handle.context().address) + .log3( + SELECTOR_LOG_TRANSFER, + from, + to, + EvmDataWriter::new().write(amount).build(), + ) + .record(handle)?; + + // Build output. + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn name() -> EvmResult { + // Build output. + Ok(succeed( + EvmDataWriter::new() + .write::(Metadata::name().into()) + .build(), + )) + } + + fn symbol() -> EvmResult { + // Build output. + Ok(succeed( + EvmDataWriter::new() + .write::(Metadata::symbol().into()) + .build(), + )) + } + + fn decimals() -> EvmResult { + // Build output. + Ok(succeed( + EvmDataWriter::new().write(Metadata::decimals()).build(), + )) + } + + fn deposit(handle: &mut impl PrecompileHandle) -> EvmResult { + // Deposit only makes sense for the native currency. + if !Metadata::is_native_currency() { + return Err(revert("unknown selector")); + } + + let caller: Runtime::AccountId = + Runtime::AddressMapping::into_account_id(handle.context().caller); + let precompile = Runtime::AddressMapping::into_account_id(handle.context().address); + let amount = Self::u256_to_amount(handle.context().apparent_value)?; + + if amount.into() == U256::from(0u32) { + return Err(revert("deposited amount must be non-zero")); + } + + handle.record_log_costs_manual(2, 32)?; + + // Send back funds received by the precompile. + RuntimeHelper::::try_dispatch( + handle, + Some(precompile).into(), + pallet_balances::Call::::transfer { + dest: Runtime::Lookup::unlookup(caller), + value: amount, + }, + )?; + + LogsBuilder::new(handle.context().address) + .log2( + SELECTOR_LOG_DEPOSIT, + handle.context().caller, + EvmDataWriter::new() + .write(handle.context().apparent_value) + .build(), + ) + .record(handle)?; + + Ok(succeed([])) + } + + fn withdraw(handle: &mut impl PrecompileHandle) -> EvmResult { + // Withdraw only makes sense for the native currency. + if !Metadata::is_native_currency() { + return Err(revert("unknown selector")); + } + + handle.record_log_costs_manual(2, 32)?; + + let mut input = handle.read_input()?; + let withdrawn_amount: U256 = input.read()?; + + let account_amount: U256 = { + let owner: Runtime::AccountId = + Runtime::AddressMapping::into_account_id(handle.context().caller); + pallet_balances::Pallet::::usable_balance(&owner).into() + }; + + if withdrawn_amount > account_amount { + return Err(revert("trying to withdraw more than owned")); + } + + LogsBuilder::new(handle.context().address) + .log2( + SELECTOR_LOG_WITHDRAWAL, + handle.context().caller, + EvmDataWriter::new().write(withdrawn_amount).build(), + ) + .record(handle)?; + + Ok(succeed([])) + } + + fn u256_to_amount(value: U256) -> EvmResult> { + value + .try_into() + .map_err(|_| revert("amount is too large for provided balance type")) + } +} diff --git a/precompiles/balances-erc20/src/mock.rs b/precompiles/balances-erc20/src/mock.rs new file mode 100644 index 000000000..fe968671d --- /dev/null +++ b/precompiles/balances-erc20/src/mock.rs @@ -0,0 +1,299 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(unused_imports, dead_code)] +use super::*; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{construct_runtime, parameter_types, traits::Everything}; +use pallet_evm::{AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileSet}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +pub type AccountId = Account; +pub type Balance = u128; +pub type BlockNumber = u64; +pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +pub type Block = frame_system::mocking::MockBlock; + +pub const PRECOMPILE_ADDRESS: u64 = 1; + +/// To test EIP2612 permits we need to have cryptographic accounts. +pub const ALICE_PUBLIC_KEY: [u8; 20] = + hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"); + +/// To test EIP2612 permits we need to have cryptographic accounts. +pub const ALICE_SECRET_KEY: [u8; 32] = + hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"); + +/// A simple account type. +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + Serialize, + Deserialize, + derive_more::Display, + TypeInfo, +)] +pub enum Account { + Alice, + Bob, + Charlie, + Bogus, + Precompile, +} + +impl Default for Account { + fn default() -> Self { + Self::Bogus + } +} + +impl AddressMapping for Account { + fn into_account_id(h160_account: H160) -> Account { + match h160_account { + a if a == H160::from(&ALICE_PUBLIC_KEY) => Self::Alice, + a if a == H160::repeat_byte(0xBB) => Self::Bob, + a if a == H160::repeat_byte(0xCC) => Self::Charlie, + a if a == H160::from_low_u64_be(PRECOMPILE_ADDRESS) => Self::Precompile, + _ => Self::Bogus, + } + } +} + +impl From for H160 { + fn from(x: Account) -> H160 { + match x { + Account::Alice => H160::from(&ALICE_PUBLIC_KEY), + Account::Bob => H160::repeat_byte(0xBB), + Account::Charlie => H160::repeat_byte(0xCC), + Account::Precompile => H160::from_low_u64_be(PRECOMPILE_ADDRESS), + Account::Bogus => Default::default(), + } + } +} + +impl From for Account { + fn from(x: H160) -> Account { + Account::into_account_id(x) + } +} + +impl From for H256 { + fn from(x: Account) -> H256 { + let x: H160 = x.into(); + x.into() + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 0; +} + +impl pallet_balances::Config for Runtime { + type MaxReserves = (); + type ReserveIdentifier = (); + type MaxLocks = (); + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub const PrecompilesValue: Precompiles = Precompiles(PhantomData); +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = (); + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type Event = Event; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); +} + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Evm: pallet_evm::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +/// ERC20 metadata for the native token. +pub struct NativeErc20Metadata; + +impl Erc20Metadata for NativeErc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str { + "Mock token" + } + + /// Returns the symbol of the token. + fn symbol() -> &'static str { + "MOCK" + } + + /// Returns the decimals places of the token. + fn decimals() -> u8 { + 18 + } + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool { + true + } +} + +#[derive(Default)] +pub struct Precompiles(PhantomData); + +impl PrecompileSet for Precompiles +where + Erc20BalancesPrecompile: Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option> { + match handle.code_address() { + a if a == hash(PRECOMPILE_ADDRESS) => Some(Erc20BalancesPrecompile::< + R, + NativeErc20Metadata, + >::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: H160) -> bool { + address == hash(PRECOMPILE_ADDRESS) + } +} + +fn hash(a: u64) -> H160 { + H160::from_low_u64_be(a) +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +pub(crate) fn events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .collect::>() +} diff --git a/precompiles/balances-erc20/src/tests.rs b/precompiles/balances-erc20/src/tests.rs new file mode 100644 index 000000000..6987d2b6a --- /dev/null +++ b/precompiles/balances-erc20/src/tests.rs @@ -0,0 +1,1312 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::str::from_utf8; + +use crate::{eip2612::Eip2612, mock::*, *}; +use precompile_utils::PrecompileTesterExt; + +use libsecp256k1::{sign, Message, SecretKey}; +use sha3::{Digest, Keccak256}; +use sp_core::{H256, U256}; + +// No test of invalid selectors since we have a fallback behavior (deposit). +fn precompiles() -> Precompiles { + PrecompilesValue::get() +} + +#[test] +fn selectors() { + assert_eq!(Action::BalanceOf as u32, 0x70a08231); + assert_eq!(Action::TotalSupply as u32, 0x18160ddd); + assert_eq!(Action::Approve as u32, 0x095ea7b3); + assert_eq!(Action::Allowance as u32, 0xdd62ed3e); + assert_eq!(Action::Transfer as u32, 0xa9059cbb); + assert_eq!(Action::TransferFrom as u32, 0x23b872dd); + assert_eq!(Action::Name as u32, 0x06fdde03); + assert_eq!(Action::Symbol as u32, 0x95d89b41); + assert_eq!(Action::Deposit as u32, 0xd0e30db0); + assert_eq!(Action::Withdraw as u32, 0x2e1a7d4d); + assert_eq!(Action::Eip2612Nonces as u32, 0x7ecebe00); + assert_eq!(Action::Eip2612Permit as u32, 0xd505accf); + assert_eq!(Action::Eip2612DomainSeparator as u32, 0x3644e515); + + assert_eq!( + crate::SELECTOR_LOG_TRANSFER, + &Keccak256::digest(b"Transfer(address,address,uint256)")[..] + ); + + assert_eq!( + crate::SELECTOR_LOG_APPROVAL, + &Keccak256::digest(b"Approval(address,address,uint256)")[..] + ); + + assert_eq!( + crate::SELECTOR_LOG_DEPOSIT, + &Keccak256::digest(b"Deposit(address,uint256)")[..] + ); + + assert_eq!( + crate::SELECTOR_LOG_WITHDRAWAL, + &Keccak256::digest(b"Withdrawal(address,uint256)")[..] + ); +} + +#[test] +fn get_total_supply() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::TotalSupply).build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(3500u64)).build()); + }); +} + +#[test] +fn get_balances_known_user() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1000u64)).build()); + }); +} + +#[test] +fn get_balances_unknown_user() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u64)).build()); + }); +} + +#[test] +fn approve() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Approve) + .write(Address(Account::Bob.into())) + .write(U256::from(500)) + .build(), + ) + .expect_cost(1756) + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_APPROVAL, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(500)).build(), + )) + .execute_returns(EvmDataWriter::new().write(true).build()); + }); +} + +#[test] +fn approve_saturating() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Approve) + .write(Address(Account::Bob.into())) + .write(U256::MAX) + .build(), + ) + .expect_cost(1756u64) + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_APPROVAL, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::MAX).build(), + )) + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(u128::MAX)).build()); + }); +} + +#[test] +fn check_allowance_existing() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Approve) + .write(Address(Account::Bob.into())) + .write(U256::from(500)) + .build(), + ) + .execute_some(); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(500u64)).build()); + }); +} + +#[test] +fn check_allowance_not_existing() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u64)).build()); + }); +} + +#[test] +fn transfer() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Transfer) + .write(Address(Account::Bob.into())) + .write(U256::from(400)) + .build(), + ) + .expect_cost(166861756u64) // 1 weight => 1 gas in mock + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_TRANSFER, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(400)).build(), + )) + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + }); +} + +#[test] +fn transfer_not_enough_funds() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Transfer) + .write(Address(Account::Bob.into())) + .write(U256::from(1400)) + .build(), + ) + .execute_reverts(|output| { + from_utf8(&output) + .unwrap() + .contains("Dispatched call failed with error: DispatchErrorWithPostInfo") + && from_utf8(&output).unwrap().contains("InsufficientBalance") + }); + }); +} + +#[test] +fn transfer_from() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Approve) + .write(Address(Account::Bob.into())) + .write(U256::from(500)) + .build(), + ) + .execute_some(); + + precompiles() + .prepare_test( + Account::Bob, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::TransferFrom) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .write(U256::from(400)) + .build(), + ) + .expect_cost(166861756u64) // 1 weight => 1 gas in mock + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_TRANSFER, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(400)).build(), + )) + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(100u64)).build()); + }); +} + +#[test] +fn transfer_from_above_allowance() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Approve) + .write(Address(Account::Bob.into())) + .write(U256::from(300)) + .build(), + ) + .execute_some(); + + precompiles() + .prepare_test( + Account::Bob, // Bob is the one sending transferFrom! + Account::Precompile, + EvmDataWriter::new_with_selector(Action::TransferFrom) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .write(U256::from(400)) + .build(), + ) + .execute_reverts(|output| output == b"trying to spend more than allowed"); + }); +} + +#[test] +fn transfer_from_self() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, // Alice sending transferFrom herself, no need for allowance. + Account::Precompile, + EvmDataWriter::new_with_selector(Action::TransferFrom) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .write(U256::from(400)) + .build(), + ) + .expect_cost(166861756u64) // 1 weight => 1 gas in mock + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_TRANSFER, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(400)).build(), + )) + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + }); +} + +#[test] +fn get_metadata_name() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Name).build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns( + EvmDataWriter::new() + .write::("Mock token".into()) + .build(), + ); + }); +} + +#[test] +fn get_metadata_symbol() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Symbol).build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write::("MOCK".into()).build()); + }); +} + +#[test] +fn get_metadata_decimals() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Decimals).build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(18u8).build()); + }); +} + +fn deposit(data: Vec) { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + // Check precompile balance is 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Deposit + // We need to call using EVM pallet so we can check the EVM correctly sends the amount + // to the precompile. + Evm::call( + Origin::root(), + Account::Alice.into(), + Account::Precompile.into(), + data, + From::from(500), // amount sent + u64::MAX, // gas limit + 0u32.into(), // gas price + None, // max priority + None, // nonce + vec![], // access list + ) + .expect("it works"); + + assert_eq!( + events(), + vec![ + Event::System(frame_system::Event::NewAccount { + account: Account::Precompile + }), + Event::Balances(pallet_balances::Event::Endowed { + account: Account::Precompile, + free_balance: 500 + }), + // EVM make a transfer because some value is provided. + Event::Balances(pallet_balances::Event::Transfer { + from: Account::Alice, + to: Account::Precompile, + amount: 500 + }), + // Precompile send it back since deposit should be a no-op. + Event::Balances(pallet_balances::Event::Transfer { + from: Account::Precompile, + to: Account::Alice, + amount: 500 + }), + // Log is correctly emitted. + Event::Evm(pallet_evm::Event::Log( + LogsBuilder::new(Account::Precompile.into()).log2( + SELECTOR_LOG_DEPOSIT, + Account::Alice, + EvmDataWriter::new().write(U256::from(500)).build(), + ) + )), + Event::Evm(pallet_evm::Event::Executed(Account::Precompile.into())), + ] + ); + + // Check precompile balance is still 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Check Alice balance is still 1000. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1000)).build()); + }); +} + +#[test] +fn deposit_function() { + deposit(EvmDataWriter::new_with_selector(Action::Deposit).build()) +} + +#[test] +fn deposit_fallback() { + deposit(EvmDataWriter::new_with_selector(0x01234567u32).build()) +} + +#[test] +fn deposit_receive() { + deposit(vec![]) +} + +#[test] +fn deposit_zero() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + // Check precompile balance is 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Deposit + // We need to call using EVM pallet so we can check the EVM correctly sends the amount + // to the precompile. + Evm::call( + Origin::root(), + Account::Alice.into(), + Account::Precompile.into(), + EvmDataWriter::new_with_selector(Action::Deposit).build(), + From::from(0), // amount sent + u64::MAX, // gas limit + 0u32.into(), // gas price + None, // max priority + None, // nonce + vec![], // access list + ) + .expect("it works"); + + assert_eq!( + events(), + vec![Event::Evm(pallet_evm::Event::ExecutedFailed( + Account::Precompile.into() + )),] + ); + + // Check precompile balance is still 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Check Alice balance is still 1000. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1000)).build()); + }); +} + +#[test] +fn withdraw() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + // Check precompile balance is 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Withdraw + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Withdraw) + .write(U256::from(500)) + .build(), + ) + .expect_cost(1381) + .expect_log(LogsBuilder::new(Account::Precompile.into()).log2( + SELECTOR_LOG_WITHDRAWAL, + Account::Alice, + EvmDataWriter::new().write(U256::from(500)).build(), + )) + .execute_returns(vec![]); + + // Check Alice balance is still 1000. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1000)).build()); + }); +} + +#[test] +fn withdraw_more_than_owned() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + // Check precompile balance is 0. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Precompile.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + + // Withdraw + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Withdraw) + .write(U256::from(1001)) + .build(), + ) + .execute_reverts(|output| output == b"trying to withdraw more than owned"); + + // Check Alice balance is still 1000. + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::BalanceOf) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1000)).build()); + }); +} + +#[test] +fn permit_valid() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + let owner: H160 = Account::Alice.into(); + let spender: H160 = Account::Bob.into(); + let value: U256 = 500u16.into(); + let deadline: U256 = 0u8.into(); // todo: proper timestamp + + let permit = Eip2612::::generate_permit( + Account::Precompile.into(), + owner, + spender, + value, + 0u8.into(), // nonce + deadline, + ); + + let secret_key = SecretKey::parse(&ALICE_SECRET_KEY).unwrap(); + let message = Message::parse(&permit); + let (rs, v) = sign(&message, &secret_key); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + + precompiles() + .prepare_test( + Account::Charlie, // can be anyone + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Permit) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(deadline) + .write(v.serialize()) + .write(H256::from(rs.r.b32())) + .write(H256::from(rs.s.b32())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_APPROVAL, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(value)).build(), + )) + .execute_returns(vec![]); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(500u16)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(1u8)).build()); + }); +} + +#[test] +fn permit_invalid_nonce() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + let owner: H160 = Account::Alice.into(); + let spender: H160 = Account::Bob.into(); + let value: U256 = 500u16.into(); + let deadline: U256 = 0u8.into(); + + let permit = Eip2612::::generate_permit( + Account::Precompile.into(), + owner, + spender, + value, + 1u8.into(), // nonce + deadline, + ); + + let secret_key = SecretKey::parse(&ALICE_SECRET_KEY).unwrap(); + let message = Message::parse(&permit); + let (rs, v) = sign(&message, &secret_key); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + + precompiles() + .prepare_test( + Account::Charlie, // can be anyone + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Permit) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(deadline) + .write(v.serialize()) + .write(H256::from(rs.r.b32())) + .write(H256::from(rs.s.b32())) + .build(), + ) + .execute_reverts(|output| output == b"invalid permit"); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u16)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + }); +} + +#[test] +fn permit_invalid_signature() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + let owner: H160 = Account::Alice.into(); + let spender: H160 = Account::Bob.into(); + let value: U256 = 500u16.into(); + let deadline: U256 = 0u8.into(); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + + precompiles() + .prepare_test( + Account::Charlie, // can be anyone + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Permit) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(deadline) + .write(0u8) + .write(H256::repeat_byte(0x11)) + .write(H256::repeat_byte(0x11)) + .build(), + ) + .execute_reverts(|output| output == b"invalid permit"); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u16)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + }); +} + +#[test] +fn permit_invalid_deadline() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + pallet_timestamp::Pallet::::set_timestamp(10_000); + + let owner: H160 = Account::Alice.into(); + let spender: H160 = Account::Bob.into(); + let value: U256 = 500u16.into(); + let deadline: U256 = 5u8.into(); // deadline < timestamp => expired + + let permit = Eip2612::::generate_permit( + Account::Precompile.into(), + owner, + spender, + value, + 0u8.into(), // nonce + deadline, + ); + + let secret_key = SecretKey::parse(&ALICE_SECRET_KEY).unwrap(); + let message = Message::parse(&permit); + let (rs, v) = sign(&message, &secret_key); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + + precompiles() + .prepare_test( + Account::Charlie, // can be anyone + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Permit) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(deadline) + .write(v.serialize()) + .write(H256::from(rs.r.b32())) + .write(H256::from(rs.s.b32())) + .build(), + ) + .execute_reverts(|output| output == b"permit expired"); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Allowance) + .write(Address(Account::Alice.into())) + .write(Address(Account::Bob.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u16)).build()); + + precompiles() + .prepare_test( + Account::Alice, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Nonces) + .write(Address(Account::Alice.into())) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(U256::from(0u8)).build()); + }); +} + +// This test checks the validity of a metamask signed message against the permit precompile +// The code used to generate the signature is the following. +// You will need to import ALICE_PRIV_KEY in metamask. +// If you put this code in the developer tools console, it will log the signature +/* +await window.ethereum.enable(); +const accounts = await window.ethereum.request({ method: "eth_requestAccounts" }); + +const value = 1000; + +const fromAddress = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; +const deadline = 1; +const nonce = 0; +const spender = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +const from = accounts[0]; + +const createPermitMessageData = function () { + const message = { + owner: from, + spender: spender, + value: value, + nonce: nonce, + deadline: deadline, + }; + + const typedData = JSON.stringify({ + types: { + EIP712Domain: [ + { + name: "name", + type: "string", + }, + { + name: "version", + type: "string", + }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + Permit: [ + { + name: "owner", + type: "address", + }, + { + name: "spender", + type: "address", + }, + { + name: "value", + type: "uint256", + }, + { + name: "nonce", + type: "uint256", + }, + { + name: "deadline", + type: "uint256", + }, + ], + }, + primaryType: "Permit", + domain: { + name: "Mock token", + version: "1", + chainId: 0, + verifyingContract: "0x0000000000000000000000000000000000000001", + }, + message: message, + }); + + return { + typedData, + message, + }; +}; + +const method = "eth_signTypedData_v4" +const messageData = createPermitMessageData(); +const params = [from, messageData.typedData]; + +web3.currentProvider.sendAsync( + { + method, + params, + from, + }, + function (err, result) { + if (err) return console.dir(err); + if (result.error) { + alert(result.error.message); + } + if (result.error) return console.error('ERROR', result); + console.log('TYPED SIGNED:' + JSON.stringify(result.result)); + + const recovered = sigUtil.recoverTypedSignature_v4({ + data: JSON.parse(msgParams), + sig: result.result, + }); + + if ( + ethUtil.toChecksumAddress(recovered) === ethUtil.toChecksumAddress(from) + ) { + alert('Successfully recovered signer as ' + from); + } else { + alert( + 'Failed to verify signer when comparing ' + result + ' to ' + from + ); + } + } +); +*/ + +#[test] +fn permit_valid_with_metamask_signed_data() { + ExtBuilder::default() + .with_balances(vec![(Account::Alice, 1000)]) + .build() + .execute_with(|| { + let owner: H160 = H160::from_slice(ALICE_PUBLIC_KEY.as_slice()); + let spender: H160 = Account::Bob.into(); + let value: U256 = 1000u16.into(); + let deadline: U256 = 1u16.into(); // todo: proper timestamp + + let rsv = hex_literal::hex!( + "612960858951e133d05483804be5456a030be4ce6c000a855d865c0be75a8fc11d89ca96d5a153e8c + 7155ab1147f0f6d3326388b8d866c2406ce34567b7501a01b" + ) + .as_slice(); + let (r, sv) = rsv.split_at(32); + let (s, v) = sv.split_at(32); + let v_real = v[0]; + let r_real: [u8; 32] = r.try_into().unwrap(); + let s_real: [u8; 32] = s.try_into().unwrap(); + + precompiles() + .prepare_test( + Account::Charlie, // can be anyone, + Account::Precompile, + EvmDataWriter::new_with_selector(Action::Eip2612Permit) + .write(Address(owner)) + .write(Address(spender)) + .write(value) + .write(deadline) + .write(v_real) + .write(H256::from(r_real)) + .write(H256::from(s_real)) + .build(), + ) + .expect_cost(0) // TODO: Test db read/write costs + .expect_log(LogsBuilder::new(Account::Precompile.into()).log3( + SELECTOR_LOG_APPROVAL, + Account::Alice, + Account::Bob, + EvmDataWriter::new().write(U256::from(1000)).build(), + )) + .execute_returns(vec![]); + }); +} + +// #[test] +// fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { +// for file in ["ERC20.sol", "Permit.sol"] { +// for solidity_fn in solidity::get_selectors(file) { +// assert_eq!( +// solidity_fn.compute_selector_hex(), +// solidity_fn.docs_selector, +// "documented selector for '{}' did not match for file '{}'", +// solidity_fn.signature(), +// file, +// ); + +// let selector = solidity_fn.compute_selector(); +// if Action::try_from(selector).is_err() { +// panic!( +// "failed decoding selector 0x{:x} => '{}' as Action for file '{}'", +// selector, +// solidity_fn.signature(), +// file, +// ) +// } +// } +// } +// } diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 616908fea..d04f43734 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -24,8 +24,8 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v # Frontier evm = { git = "https://github.com/rust-blockchain/evm", rev = "01bcbd2205a212c34451d3b4fabc962793b057d3", default-features = false, features = ["with-codec"] } -fp-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } +fp-evm = { version='3.0.0-dev', default-features = false } +pallet-evm = { version='6.0.0-dev', default-features = false } # Polkadot / XCM xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.26", default-features = false } diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 30549f571..860ddcdf5 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -37,6 +37,8 @@ pub use precompile_utils_macro::{generate_function_selector, keccak256}; #[cfg(feature = "testing")] pub mod testing; +#[cfg(feature = "testing")] +pub use testing::*; #[cfg(test)] mod tests; diff --git a/runtime/kerria/Cargo.toml b/runtime/kerria/Cargo.toml index 8b540f812..0bf5c0f95 100644 --- a/runtime/kerria/Cargo.toml +++ b/runtime/kerria/Cargo.toml @@ -89,18 +89,18 @@ orml-xcm-support = { version = '0.4.1-dev', default-features = false orml-xtokens = { version = '0.4.1-dev', default-features = false } # Frontier dependencies -fp-rpc = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -fp-self-contained = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-base-fee = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-ethereum = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-blake2 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-bn128 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-dispatch = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-ed25519 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-modexp = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-sha3fips = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-simple = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } +fp-rpc = { version='3.0.0-dev', default-features = false } +fp-self-contained = { version='1.0.0-dev', default-features = false } +pallet-base-fee = { version='1.0.0', default-features = false } +pallet-ethereum = { version='4.0.0-dev', default-features = false } +pallet-evm = { version='6.0.0-dev', default-features = false } +pallet-evm-precompile-blake2 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-bn128 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-dispatch = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-ed25519 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-modexp = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-sha3fips = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-simple = { version='2.0.0-dev', default-features = false } # Parallel dependencies pallet-amm = { path = '../../pallets/amm', default-features = false } @@ -121,6 +121,7 @@ pallet-traits = { path = '../../pallets/traits', default-feature pallet-xcm-helper = { path = '../../pallets/xcm-helper', default-features = false } primitives = { package = 'parallel-primitives', path = '../../primitives', default-features = false } pallet-evm-precompile-assets-erc20 = { path = '../../precompiles/assets-erc20', default-features = false } +pallet-evm-precompile-balances-erc20 = { path = '../../precompiles/balances-erc20', default-features = false } [build-dependencies.substrate-wasm-builder] branch = 'polkadot-v0.9.26' @@ -246,6 +247,7 @@ std = [ 'pallet-evm-precompile-modexp/std', 'pallet-evm-precompile-sha3fips/std', 'pallet-evm-precompile-assets-erc20/std', + 'pallet-evm-precompile-balances-erc20/std', ] try-runtime = [ 'frame-support/try-runtime', diff --git a/runtime/kerria/src/lib.rs b/runtime/kerria/src/lib.rs index 4cfc7429a..d233ab352 100644 --- a/runtime/kerria/src/lib.rs +++ b/runtime/kerria/src/lib.rs @@ -116,10 +116,37 @@ use primitives::{ Signature, DOT_U, }; +use pallet_evm_precompile_balances_erc20::Erc20Metadata; + mod precompiles; -pub use precompiles::{ParallelPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = ParallelPrecompiles; use pallet_evm_precompile_assets_erc20::AddressToAssetId; +pub use precompiles::{ParallelPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; + +pub struct NativeErc20Metadata; + +/// ERC20 metadata for the native token. +impl Erc20Metadata for NativeErc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str { + "PARA token" + } + + /// Returns the symbol of the token. + fn symbol() -> &'static str { + "PARA" + } + + /// Returns the decimals places of the token. + fn decimals() -> u8 { + 12 + } + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool { + true + } +} // Make the WASM binary available. #[cfg(feature = "std")] @@ -987,6 +1014,8 @@ impl> FindAuthor for FindAuthorTruncated { } } +pub type ParallelPrecompilesType = ParallelPrecompiles; + parameter_types! { /// Ethereum-compatible chain_id: /// * Vanilla: 592 @@ -995,7 +1024,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = ParallelPrecompiles::<_>::new(); + pub ParallelPrecompilesValue: ParallelPrecompilesType = ParallelPrecompiles::::new(); } impl pallet_evm::Config for Runtime { @@ -1008,8 +1037,8 @@ impl pallet_evm::Config for Runtime { type Currency = Balances; type Event = Event; type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = Precompiles; - type PrecompilesValue = PrecompilesValue; + type PrecompilesType = ParallelPrecompilesType; + type PrecompilesValue = ParallelPrecompilesValue; type ChainId = EVMChainId; type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; type BlockGasLimit = BlockGasLimit; diff --git a/runtime/kerria/src/precompiles.rs b/runtime/kerria/src/precompiles.rs index 484c07db2..be7e34556 100644 --- a/runtime/kerria/src/precompiles.rs +++ b/runtime/kerria/src/precompiles.rs @@ -12,14 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use frame_support::weights::GetDispatchInfo; +use frame_support::weights::PostDispatchInfo; use pallet_evm::{ ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, }; use sp_core::H160; +use sp_runtime::traits::Dispatchable; use sp_std::fmt::Debug; use sp_std::marker::PhantomData; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_balances_erc20::Erc20BalancesPrecompile; +use pallet_evm_precompile_balances_erc20::Erc20Metadata; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dispatch::Dispatch; @@ -33,29 +38,40 @@ use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripe pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; #[derive(Debug, Default, Clone, Copy)] -pub struct ParallelPrecompiles(PhantomData); +pub struct ParallelPrecompiles(PhantomData<(R, M)>); -impl ParallelPrecompiles +impl ParallelPrecompiles where R: pallet_evm::Config, + M: Erc20Metadata, { pub fn new() -> Self { Self(Default::default()) } pub fn used_addresses() -> impl Iterator { - sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027] + sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 2050] .into_iter() .map(hash) } } -impl PrecompileSet for ParallelPrecompiles +impl PrecompileSet for ParallelPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, + Erc20BalancesPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config + AddressToAssetId<::AssetId> - + pallet_assets::Config, + + pallet_assets::Config + + pallet_balances::Config, + R::Call: Dispatchable + GetDispatchInfo, + ::Call: From>, + <::Call as Dispatchable>::Origin: + From::AccountId>>, + ::Balance: TryFrom, + ::Balance: Into, + ::Moment: Into, + M: Erc20Metadata, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { let address = handle.code_address(); @@ -81,6 +97,8 @@ where a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), + //Parallel precompiles: + a if a == hash(2050) => Some(Erc20BalancesPrecompile::::execute(handle)), a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) } diff --git a/runtime/vanilla/Cargo.toml b/runtime/vanilla/Cargo.toml index b057381ca..addc1ca7a 100644 --- a/runtime/vanilla/Cargo.toml +++ b/runtime/vanilla/Cargo.toml @@ -89,18 +89,18 @@ orml-xcm-support = { version = '0.4.1-dev', default-features = false orml-xtokens = { version = '0.4.1-dev', default-features = false } # Frontier dependencies -fp-rpc = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -fp-self-contained = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-base-fee = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-ethereum = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-blake2 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-bn128 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-dispatch = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-ed25519 = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-modexp = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-sha3fips = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } -pallet-evm-precompile-simple = { git = "https://github.com/parallel-finance/frontier", branch = "polkadot-v0.9.26", default-features = false } +fp-rpc = { version='3.0.0-dev', default-features = false } +fp-self-contained = { version='1.0.0-dev', default-features = false } +pallet-base-fee = { version='1.0.0', default-features = false } +pallet-ethereum = { version='4.0.0-dev', default-features = false } +pallet-evm = { version='6.0.0-dev', default-features = false } +pallet-evm-precompile-blake2 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-bn128 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-dispatch = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-ed25519 = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-modexp = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-sha3fips = { version='2.0.0-dev', default-features = false } +pallet-evm-precompile-simple = { version='2.0.0-dev', default-features = false } # Parallel dependencies pallet-amm = { path = '../../pallets/amm', default-features = false } @@ -122,7 +122,7 @@ pallet-xcm-helper = { path = '../../pallets/xcm-helper', default-fea pallet-stableswap = { path = '../../pallets/stableswap', default-features = false } primitives = { package = 'parallel-primitives', path = '../../primitives', default-features = false } pallet-evm-precompile-assets-erc20 = { path = '../../precompiles/assets-erc20', default-features = false } - +pallet-evm-precompile-balances-erc20 = { path = '../../precompiles/balances-erc20', default-features = false } [build-dependencies.substrate-wasm-builder] branch = 'polkadot-v0.9.26' @@ -164,17 +164,17 @@ std = [ 'serde', 'scale-info/std', 'fp-rpc/std', - 'fp-self-contained/std', + 'fp-self-contained/std', 'pallet-base-fee/std', - 'pallet-ethereum/std', - 'pallet-evm/std', - 'pallet-evm-precompile-blake2/std', - 'pallet-evm-precompile-simple/std', - 'pallet-evm-precompile-bn128/std', - 'pallet-evm-precompile-dispatch/std', - 'pallet-evm-precompile-ed25519/std', - 'pallet-evm-precompile-modexp/std', - 'pallet-evm-precompile-sha3fips/std', + 'pallet-ethereum/std', + 'pallet-evm/std', + 'pallet-evm-precompile-blake2/std', + 'pallet-evm-precompile-simple/std', + 'pallet-evm-precompile-bn128/std', + 'pallet-evm-precompile-dispatch/std', + 'pallet-evm-precompile-ed25519/std', + 'pallet-evm-precompile-modexp/std', + 'pallet-evm-precompile-sha3fips/std', 'sp-api/std', 'sp-std/std', 'sp-core/std', @@ -253,6 +253,7 @@ std = [ 'pallet-asset-registry/std', 'pallet-traits/std', 'pallet-evm-precompile-assets-erc20/std', + 'pallet-evm-precompile-balances-erc20/std', ] try-runtime = [ 'frame-support/try-runtime', diff --git a/runtime/vanilla/src/lib.rs b/runtime/vanilla/src/lib.rs index ca1dfcb58..b1f49aef3 100644 --- a/runtime/vanilla/src/lib.rs +++ b/runtime/vanilla/src/lib.rs @@ -118,10 +118,37 @@ use primitives::{ Signature, KSM_U, }; +use pallet_evm_precompile_balances_erc20::Erc20Metadata; + mod precompiles; -pub use precompiles::{ParallelPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = ParallelPrecompiles; use pallet_evm_precompile_assets_erc20::AddressToAssetId; +pub use precompiles::{ParallelPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; + +pub struct NativeErc20Metadata; + +/// ERC20 metadata for the native token. +impl Erc20Metadata for NativeErc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str { + "HKO token" + } + + /// Returns the symbol of the token. + fn symbol() -> &'static str { + "HKO" + } + + /// Returns the decimals places of the token. + fn decimals() -> u8 { + 12 + } + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool { + true + } +} // Make the WASM binary available. #[cfg(feature = "std")] @@ -989,6 +1016,8 @@ impl> FindAuthor for FindAuthorTruncated { } } +pub type ParallelPrecompilesType = ParallelPrecompiles; + parameter_types! { /// Ethereum-compatible chain_id: /// * Vanilla: 592 @@ -997,7 +1026,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = ParallelPrecompiles::<_>::new(); + pub ParallelPrecompilesValue: ParallelPrecompilesType = ParallelPrecompiles::::new(); } impl pallet_evm::Config for Runtime { @@ -1010,8 +1039,8 @@ impl pallet_evm::Config for Runtime { type Currency = Balances; type Event = Event; type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = Precompiles; - type PrecompilesValue = PrecompilesValue; + type PrecompilesType = ParallelPrecompilesType; + type PrecompilesValue = ParallelPrecompilesValue; type ChainId = EVMChainId; type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; type BlockGasLimit = BlockGasLimit; diff --git a/runtime/vanilla/src/precompiles.rs b/runtime/vanilla/src/precompiles.rs index 484c07db2..be7e34556 100644 --- a/runtime/vanilla/src/precompiles.rs +++ b/runtime/vanilla/src/precompiles.rs @@ -12,14 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use frame_support::weights::GetDispatchInfo; +use frame_support::weights::PostDispatchInfo; use pallet_evm::{ ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, }; use sp_core::H160; +use sp_runtime::traits::Dispatchable; use sp_std::fmt::Debug; use sp_std::marker::PhantomData; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_balances_erc20::Erc20BalancesPrecompile; +use pallet_evm_precompile_balances_erc20::Erc20Metadata; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dispatch::Dispatch; @@ -33,29 +38,40 @@ use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripe pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; #[derive(Debug, Default, Clone, Copy)] -pub struct ParallelPrecompiles(PhantomData); +pub struct ParallelPrecompiles(PhantomData<(R, M)>); -impl ParallelPrecompiles +impl ParallelPrecompiles where R: pallet_evm::Config, + M: Erc20Metadata, { pub fn new() -> Self { Self(Default::default()) } pub fn used_addresses() -> impl Iterator { - sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027] + sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 2050] .into_iter() .map(hash) } } -impl PrecompileSet for ParallelPrecompiles +impl PrecompileSet for ParallelPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, + Erc20BalancesPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config + AddressToAssetId<::AssetId> - + pallet_assets::Config, + + pallet_assets::Config + + pallet_balances::Config, + R::Call: Dispatchable + GetDispatchInfo, + ::Call: From>, + <::Call as Dispatchable>::Origin: + From::AccountId>>, + ::Balance: TryFrom, + ::Balance: Into, + ::Moment: Into, + M: Erc20Metadata, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { let address = handle.code_address(); @@ -81,6 +97,8 @@ where a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), + //Parallel precompiles: + a if a == hash(2050) => Some(Erc20BalancesPrecompile::::execute(handle)), a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) }