diff --git a/Cargo.lock b/Cargo.lock index ccee20011eab0..1ef6f1565eb68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14304,6 +14304,7 @@ dependencies = [ "git2", "hex", "jsonrpsee", + "k256", "libsqlite3-sys", "log", "pallet-revive", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_revive.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_revive.rs index 0ca703495d28c..131cb74fe3a85 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_revive.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_revive.rs @@ -16,9 +16,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2026-05-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-05-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `fb71ca6c73b4`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `a974dfb416a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -56,11 +56,75 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `279` // Estimated: `1764` - // Minimum execution time: 3_271_000 picoseconds. - Weight::from_parts(3_651_000, 0) + // Minimum execution time: 3_470_000 picoseconds. + Weight::from_parts(3_807_000, 0) .saturating_add(Weight::from_parts(0, 1764)) .saturating_add(T::DbWeight::get().reads(1)) } + /// Storage: `Revive::OriginalAccount` (r:255 w:0) + /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) + /// Storage: `System::Account` (r:255 w:255) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Revive::AccountInfoOf` (r:256 w:255) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) + /// Storage: `Assets::Asset` (r:1 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `Measured`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `Measured`) + /// Storage: `Balances::Holds` (r:255 w:255) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(391), added: 2866, mode: `Measured`) + /// Storage: `Revive::NativeDepositOf` (r:255 w:255) + /// Proof: `Revive::NativeDepositOf` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `Measured`) + /// The range of component `n` is `[0, 255]`. + fn process_new_account_authorization(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `50691 + n * (1381 ±0)` + // Estimated: `50782 + n * (3866 ±24)` + // Minimum execution time: 608_000 picoseconds. + Weight::from_parts(677_000, 0) + .saturating_add(Weight::from_parts(0, 50782)) + // Standard Error: 94_536 + .saturating_add(Weight::from_parts(165_578_449, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3866).saturating_mul(n.into())) + } + /// Storage: `Revive::OriginalAccount` (r:255 w:0) + /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) + /// Storage: `System::Account` (r:255 w:255) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Revive::AccountInfoOf` (r:256 w:255) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) + /// Storage: `Assets::Asset` (r:1 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `Measured`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `Measured`) + /// Storage: `Balances::Holds` (r:255 w:255) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(391), added: 2866, mode: `Measured`) + /// Storage: `Revive::NativeDepositOf` (r:255 w:255) + /// Proof: `Revive::NativeDepositOf` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `Measured`) + /// The range of component `n` is `[0, 255]`. + fn process_existing_account_authorization(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `50525 + n * (1505 ±0)` + // Estimated: `50856 + n * (3987 ±24)` + // Minimum execution time: 613_000 picoseconds. + Weight::from_parts(658_000, 0) + .saturating_add(Weight::from_parts(0, 50856)) + // Standard Error: 84_784 + .saturating_add(Weight::from_parts(147_393_149, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3987).saturating_mul(n.into())) + } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `Revive::DeletionQueue` (r:1 w:1) @@ -71,8 +135,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `605` // Estimated: `4070` - // Minimum execution time: 22_383_000 picoseconds. - Weight::from_parts(24_407_000, 0) + // Minimum execution time: 22_847_000 picoseconds. + Weight::from_parts(24_757_000, 0) .saturating_add(Weight::from_parts(0, 4070)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -84,11 +148,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `738 + k * (69 ±0)` // Estimated: `728 + k * (70 ±0)` - // Minimum execution time: 23_192_000 picoseconds. - Weight::from_parts(9_755_497, 0) + // Minimum execution time: 23_785_000 picoseconds. + Weight::from_parts(10_332_742, 0) .saturating_add(Weight::from_parts(0, 728)) - // Standard Error: 1_074 - .saturating_add(Weight::from_parts(1_290_616, 0).saturating_mul(k.into())) + // Standard Error: 870 + .saturating_add(Weight::from_parts(1_289_540, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -102,11 +166,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `630 + k * (52 ±0)` // Estimated: `645 + k * (53 ±0)` - // Minimum execution time: 23_348_000 picoseconds. - Weight::from_parts(2_310_806, 0) + // Minimum execution time: 23_956_000 picoseconds. + Weight::from_parts(3_624_751, 0) .saturating_add(Weight::from_parts(0, 645)) - // Standard Error: 1_049 - .saturating_add(Weight::from_parts(1_295_757, 0).saturating_mul(k.into())) + // Standard Error: 1_043 + .saturating_add(Weight::from_parts(1_332_686, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -114,7 +178,7 @@ impl pallet_revive::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 53).saturating_mul(k.into())) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) @@ -130,17 +194,17 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `4965 + c * (1 ±0)` // Estimated: `10854 + c * (1 ±0)` - // Minimum execution time: 130_131_000 picoseconds. - Weight::from_parts(176_333_697, 0) + // Minimum execution time: 130_713_000 picoseconds. + Weight::from_parts(181_453_444, 0) .saturating_add(Weight::from_parts(0, 10854)) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_490, 0).saturating_mul(c.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(1_333, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) @@ -156,17 +220,17 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `4940 + c * (1 ±0)` // Estimated: `10782 + c * (1 ±0)` - // Minimum execution time: 119_135_000 picoseconds. - Weight::from_parts(127_426_989, 0) + // Minimum execution time: 119_587_000 picoseconds. + Weight::from_parts(127_509_551, 0) .saturating_add(Weight::from_parts(0, 10782)) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_554, 0).saturating_mul(c.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(1_707, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) @@ -178,20 +242,18 @@ impl pallet_revive::WeightInfo for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `b` is `[0, 1]`. - fn basic_block_compilation(b: u32, ) -> Weight { + fn basic_block_compilation(_b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `8297` // Estimated: `14237` - // Minimum execution time: 176_544_000 picoseconds. - Weight::from_parts(185_870_497, 0) + // Minimum execution time: 176_617_000 picoseconds. + Weight::from_parts(184_555_751, 0) .saturating_add(Weight::from_parts(0, 14237)) - // Standard Error: 350_282 - .saturating_add(Weight::from_parts(1_494_036, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) @@ -220,18 +282,18 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `6535` // Estimated: `15032` - // Minimum execution time: 885_213_000 picoseconds. - Weight::from_parts(185_316_278, 0) + // Minimum execution time: 862_860_000 picoseconds. + Weight::from_parts(170_027_519, 0) .saturating_add(Weight::from_parts(0, 15032)) - // Standard Error: 44 - .saturating_add(Weight::from_parts(20_584, 0).saturating_mul(c.into())) - // Standard Error: 34 - .saturating_add(Weight::from_parts(4_887, 0).saturating_mul(i.into())) + // Standard Error: 43 + .saturating_add(Weight::from_parts(20_093, 0).saturating_mul(c.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(4_998, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(12)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:2) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) @@ -265,15 +327,15 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `8522` // Estimated: `16937` - // Minimum execution time: 542_899_000 picoseconds. - Weight::from_parts(435_633_086, 0) + // Minimum execution time: 529_172_000 picoseconds. + Weight::from_parts(419_357_120, 0) .saturating_add(Weight::from_parts(0, 16937)) - // Standard Error: 52 - .saturating_add(Weight::from_parts(16_418, 0).saturating_mul(c.into())) - // Standard Error: 40 - .saturating_add(Weight::from_parts(557, 0).saturating_mul(i.into())) - // Standard Error: 3_406_390 - .saturating_add(Weight::from_parts(7_634_749, 0).saturating_mul(d.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(15_716, 0).saturating_mul(c.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(594, 0).saturating_mul(i.into())) + // Standard Error: 2_474_621 + .saturating_add(Weight::from_parts(18_657_183, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(19)) .saturating_add(T::DbWeight::get().writes(17)) } @@ -281,12 +343,12 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_241_000 picoseconds. - Weight::from_parts(3_602_000, 0) + // Minimum execution time: 3_256_000 picoseconds. + Weight::from_parts(3_610_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -314,16 +376,16 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `6132` // Estimated: `12009` - // Minimum execution time: 256_824_000 picoseconds. - Weight::from_parts(269_765_341, 0) + // Minimum execution time: 253_995_000 picoseconds. + Weight::from_parts(267_277_215, 0) .saturating_add(Weight::from_parts(0, 12009)) // Standard Error: 6 - .saturating_add(Weight::from_parts(4_200, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_183, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().writes(9)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) @@ -338,14 +400,14 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `5353` // Estimated: `11293` - // Minimum execution time: 126_293_000 picoseconds. - Weight::from_parts(133_217_000, 0) + // Minimum execution time: 127_790_000 picoseconds. + Weight::from_parts(134_053_000, 0) .saturating_add(Weight::from_parts(0, 11293)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:2) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) @@ -365,16 +427,16 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `6764` // Estimated: `12704` - // Minimum execution time: 231_411_000 picoseconds. - Weight::from_parts(244_218_628, 0) + // Minimum execution time: 233_430_000 picoseconds. + Weight::from_parts(245_523_261, 0) .saturating_add(Weight::from_parts(0, 12704)) - // Standard Error: 378_794 - .saturating_add(Weight::from_parts(2_979_957, 0).saturating_mul(d.into())) + // Standard Error: 346_989 + .saturating_add(Weight::from_parts(3_814_263, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::EthBlockBuilderIR` (r:1 w:1) /// Proof: `Revive::EthBlockBuilderIR` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Revive::EthBlockBuilderFirstValues` (r:0 w:1) @@ -384,16 +446,16 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `302` // Estimated: `3767` - // Minimum execution time: 28_271_000 picoseconds. - Weight::from_parts(23_454_099, 0) + // Minimum execution time: 27_744_000 picoseconds. + Weight::from_parts(24_731_233, 0) .saturating_add(Weight::from_parts(0, 3767)) // Standard Error: 4 - .saturating_add(Weight::from_parts(6_378, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(6_336, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:0) @@ -411,11 +473,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2305` // Estimated: `5770` - // Minimum execution time: 82_337_000 picoseconds. - Weight::from_parts(73_288_351, 0) + // Minimum execution time: 81_434_000 picoseconds. + Weight::from_parts(76_241_908, 0) .saturating_add(Weight::from_parts(0, 5770)) - // Standard Error: 22 - .saturating_add(Weight::from_parts(14_543, 0).saturating_mul(c.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(14_246, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -431,14 +493,14 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2223` // Estimated: `5688` - // Minimum execution time: 63_531_000 picoseconds. - Weight::from_parts(67_712_000, 0) + // Minimum execution time: 62_316_000 picoseconds. + Weight::from_parts(65_888_000, 0) .saturating_add(Weight::from_parts(0, 5688)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Revive::NativeDepositOf` (r:1 w:1) @@ -453,14 +515,14 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3304` // Estimated: `9244` - // Minimum execution time: 87_170_000 picoseconds. - Weight::from_parts(92_834_000, 0) + // Minimum execution time: 86_224_000 picoseconds. + Weight::from_parts(90_754_000, 0) .saturating_add(Weight::from_parts(0, 9244)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:1 w:1) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) @@ -469,8 +531,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3914` // Estimated: `7379` - // Minimum execution time: 74_770_000 picoseconds. - Weight::from_parts(79_606_000, 0) + // Minimum execution time: 72_460_000 picoseconds. + Weight::from_parts(76_517_000, 0) .saturating_add(Weight::from_parts(0, 7379)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -483,43 +545,43 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1793` // Estimated: `5258` - // Minimum execution time: 35_835_000 picoseconds. - Weight::from_parts(38_547_000, 0) + // Minimum execution time: 34_763_000 picoseconds. + Weight::from_parts(36_788_000, 0) .saturating_add(Weight::from_parts(0, 5258)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) + /// Storage: `System::Account` (r:1024 w:1024) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// Storage: `Revive::OriginalAccount` (r:1024 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Balances::Holds` (r:1024 w:1024) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(391), added: 2866, mode: `Measured`) - /// Storage: `System::Account` (r:1024 w:1024) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `a` is `[0, 1024]`. fn batch_map_accounts(a: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `238922 + a * (861 ±0)` // Estimated: `189927 + a * (3400 ±23)` - // Minimum execution time: 11_522_000 picoseconds. - Weight::from_parts(11_912_000, 0) + // Minimum execution time: 11_517_000 picoseconds. + Weight::from_parts(12_016_000, 0) .saturating_add(Weight::from_parts(0, 189927)) - // Standard Error: 48_689 - .saturating_add(Weight::from_parts(62_698_501, 0).saturating_mul(a.into())) + // Standard Error: 45_695 + .saturating_add(Weight::from_parts(61_870_837, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) .saturating_add(Weight::from_parts(0, 3400).saturating_mul(a.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) fn dispatch_as_fallback_account() -> Weight { // Proof Size summary in bytes: // Measured: `302` // Estimated: `3767` - // Minimum execution time: 13_910_000 picoseconds. - Weight::from_parts(15_353_000, 0) + // Minimum execution time: 13_862_000 picoseconds. + Weight::from_parts(15_134_000, 0) .saturating_add(Weight::from_parts(0, 3767)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -528,26 +590,26 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_526_000 picoseconds. - Weight::from_parts(13_718_215, 0) + // Minimum execution time: 12_355_000 picoseconds. + Weight::from_parts(14_101_041, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 68 - .saturating_add(Weight::from_parts(142_619, 0).saturating_mul(r.into())) + // Standard Error: 66 + .saturating_add(Weight::from_parts(141_796, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 360_000 picoseconds. - Weight::from_parts(554_000, 0) + // Minimum execution time: 422_000 picoseconds. + Weight::from_parts(607_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 380_000 picoseconds. - Weight::from_parts(575_000, 0) + // Minimum execution time: 400_000 picoseconds. + Weight::from_parts(612_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) @@ -556,19 +618,19 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1871` // Estimated: `5336` - // Minimum execution time: 14_769_000 picoseconds. + // Minimum execution time: 14_823_000 picoseconds. Weight::from_parts(15_960_000, 0) .saturating_add(Weight::from_parts(0, 5336)) .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `124` // Estimated: `3589` - // Minimum execution time: 5_264_000 picoseconds. - Weight::from_parts(5_977_000, 0) + // Minimum execution time: 7_331_000 picoseconds. + Weight::from_parts(8_139_000, 0) .saturating_add(Weight::from_parts(0, 3589)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -576,20 +638,20 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_214_000 picoseconds. - Weight::from_parts(4_926_000, 0) + // Minimum execution time: 5_977_000 picoseconds. + Weight::from_parts(6_786_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) fn seal_code_size() -> Weight { // Proof Size summary in bytes: // Measured: `194` // Estimated: `3659` - // Minimum execution time: 10_601_000 picoseconds. - Weight::from_parts(11_865_000, 0) + // Minimum execution time: 12_955_000 picoseconds. + Weight::from_parts(14_085_000, 0) .saturating_add(Weight::from_parts(0, 3659)) .saturating_add(T::DbWeight::get().reads(2)) } @@ -597,62 +659,62 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_638_000 picoseconds. - Weight::from_parts(1_985_000, 0) + // Minimum execution time: 1_796_000 picoseconds. + Weight::from_parts(2_149_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550_000 picoseconds. - Weight::from_parts(1_954_000, 0) + // Minimum execution time: 1_739_000 picoseconds. + Weight::from_parts(2_133_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 351_000 picoseconds. - Weight::from_parts(539_000, 0) + // Minimum execution time: 378_000 picoseconds. + Weight::from_parts(589_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_746_000 picoseconds. - Weight::from_parts(2_067_000, 0) + // Minimum execution time: 1_731_000 picoseconds. + Weight::from_parts(2_192_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_628_000 picoseconds. - Weight::from_parts(3_064_000, 0) + // Minimum execution time: 2_740_000 picoseconds. + Weight::from_parts(3_145_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_830_000 picoseconds. - Weight::from_parts(5_454_000, 0) + // Minimum execution time: 5_095_000 picoseconds. + Weight::from_parts(5_766_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn seal_balance_of() -> Weight { // Proof Size summary in bytes: // Measured: `3112` // Estimated: `6577` - // Minimum execution time: 26_800_000 picoseconds. - Weight::from_parts(28_768_000, 0) + // Minimum execution time: 27_317_000 picoseconds. + Weight::from_parts(28_872_000, 0) .saturating_add(Weight::from_parts(0, 6577)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -663,11 +725,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `13 + n * (1 ±0)` // Estimated: `3478 + n * (1 ±0)` - // Minimum execution time: 4_674_000 picoseconds. - Weight::from_parts(5_285_537, 0) + // Minimum execution time: 4_602_000 picoseconds. + Weight::from_parts(5_162_167, 0) .saturating_add(Weight::from_parts(0, 3478)) // Standard Error: 1 - .saturating_add(Weight::from_parts(513, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(531, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -678,75 +740,75 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_435_000 picoseconds. - Weight::from_parts(2_841_974, 0) + // Minimum execution time: 2_449_000 picoseconds. + Weight::from_parts(2_875_450, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(534, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(526, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 299_000 picoseconds. - Weight::from_parts(476_000, 0) + // Minimum execution time: 375_000 picoseconds. + Weight::from_parts(553_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_946_000 picoseconds. - Weight::from_parts(2_282_000, 0) + // Minimum execution time: 2_047_000 picoseconds. + Weight::from_parts(2_434_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 289_000 picoseconds. - Weight::from_parts(448_000, 0) + // Minimum execution time: 346_000 picoseconds. + Weight::from_parts(520_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(428_000, 0) + // Minimum execution time: 323_000 picoseconds. + Weight::from_parts(508_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 291_000 picoseconds. - Weight::from_parts(464_000, 0) + // Minimum execution time: 325_000 picoseconds. + Weight::from_parts(526_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_159_000 picoseconds. - Weight::from_parts(1_427_000, 0) + // Minimum execution time: 1_179_000 picoseconds. + Weight::from_parts(1_524_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_base_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_158_000 picoseconds. - Weight::from_parts(1_453_000, 0) + // Minimum execution time: 1_237_000 picoseconds. + Weight::from_parts(1_571_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 300_000 picoseconds. - Weight::from_parts(457_000, 0) + // Minimum execution time: 360_000 picoseconds. + Weight::from_parts(555_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Aura::Authorities` (r:1 w:0) @@ -757,8 +819,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `442` // Estimated: `1927` - // Minimum execution time: 25_292_000 picoseconds. - Weight::from_parts(26_773_000, 0) + // Minimum execution time: 25_707_000 picoseconds. + Weight::from_parts(27_164_000, 0) .saturating_add(Weight::from_parts(0, 1927)) .saturating_add(T::DbWeight::get().reads(2)) } @@ -768,8 +830,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `22` // Estimated: `3487` - // Minimum execution time: 3_581_000 picoseconds. - Weight::from_parts(3_998_000, 0) + // Minimum execution time: 3_579_000 picoseconds. + Weight::from_parts(4_006_000, 0) .saturating_add(Weight::from_parts(0, 3487)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -777,8 +839,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(444_000, 0) + // Minimum execution time: 336_000 picoseconds. + Weight::from_parts(543_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `n` is `[0, 1048572]`. @@ -786,18 +848,18 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 633_000 picoseconds. - Weight::from_parts(703_000, 0) + // Minimum execution time: 580_000 picoseconds. + Weight::from_parts(717_000, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(206, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 293_000 picoseconds. - Weight::from_parts(454_000, 0) + // Minimum execution time: 368_000 picoseconds. + Weight::from_parts(558_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `n` is `[0, 1048576]`. @@ -805,22 +867,22 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 418_000 picoseconds. - Weight::from_parts(380_308, 0) + // Minimum execution time: 354_000 picoseconds. + Weight::from_parts(197_727, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(115, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(116, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 131072]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 390_000 picoseconds. - Weight::from_parts(682_938, 0) + // Minimum execution time: 414_000 picoseconds. + Weight::from_parts(715_802, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(203, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } /// Storage: `Revive::OriginalAccount` (r:2 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -829,11 +891,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2763` // Estimated: `8703` - // Minimum execution time: 23_970_000 picoseconds. - Weight::from_parts(25_948_530, 0) + // Minimum execution time: 24_689_000 picoseconds. + Weight::from_parts(26_743_348, 0) .saturating_add(Weight::from_parts(0, 8703)) - // Standard Error: 26_738 - .saturating_add(Weight::from_parts(97_478, 0).saturating_mul(r.into())) + // Standard Error: 25_899 + .saturating_add(Weight::from_parts(189_778, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: `Balances::Holds` (r:2 w:2) @@ -870,8 +932,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `9547` // Estimated: `17962` - // Minimum execution time: 406_992_000 picoseconds. - Weight::from_parts(427_367_000, 0) + // Minimum execution time: 405_610_000 picoseconds. + Weight::from_parts(426_416_000, 0) .saturating_add(Weight::from_parts(0, 17962)) .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(17)) @@ -882,11 +944,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_538_000 picoseconds. - Weight::from_parts(6_758_000, 0) + // Minimum execution time: 6_370_000 picoseconds. + Weight::from_parts(6_816_000, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_313, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_327, 0).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -894,8 +956,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `653` // Estimated: `653` - // Minimum execution time: 8_568_000 picoseconds. - Weight::from_parts(9_436_000, 0) + // Minimum execution time: 8_746_000 picoseconds. + Weight::from_parts(9_631_000, 0) .saturating_add(Weight::from_parts(0, 653)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -905,8 +967,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `10663` // Estimated: `10663` - // Minimum execution time: 39_344_000 picoseconds. - Weight::from_parts(41_765_000, 0) + // Minimum execution time: 39_751_000 picoseconds. + Weight::from_parts(41_770_000, 0) .saturating_add(Weight::from_parts(0, 10663)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -916,8 +978,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `653` // Estimated: `653` - // Minimum execution time: 9_855_000 picoseconds. - Weight::from_parts(10_946_000, 0) + // Minimum execution time: 9_941_000 picoseconds. + Weight::from_parts(11_243_000, 0) .saturating_add(Weight::from_parts(0, 653)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -928,8 +990,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `10663` // Estimated: `10663` - // Minimum execution time: 41_728_000 picoseconds. - Weight::from_parts(43_909_000, 0) + // Minimum execution time: 41_718_000 picoseconds. + Weight::from_parts(44_058_000, 0) .saturating_add(Weight::from_parts(0, 10663)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -942,13 +1004,13 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `253 + o * (1 ±0)` // Estimated: `252 + o * (1 ±0)` - // Minimum execution time: 11_752_000 picoseconds. - Weight::from_parts(13_012_834, 0) + // Minimum execution time: 11_791_000 picoseconds. + Weight::from_parts(12_960_512, 0) .saturating_add(Weight::from_parts(0, 252)) - // Standard Error: 27 - .saturating_add(Weight::from_parts(423, 0).saturating_mul(n.into())) - // Standard Error: 27 - .saturating_add(Weight::from_parts(344, 0).saturating_mul(o.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(703, 0).saturating_mul(n.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(608, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -960,11 +1022,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `253 + n * (1 ±0)` // Estimated: `252 + n * (1 ±0)` - // Minimum execution time: 13_911_000 picoseconds. - Weight::from_parts(15_327_010, 0) + // Minimum execution time: 14_220_000 picoseconds. + Weight::from_parts(15_669_513, 0) .saturating_add(Weight::from_parts(0, 252)) - // Standard Error: 27 - .saturating_add(Weight::from_parts(958, 0).saturating_mul(n.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(866, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -976,11 +1038,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `253 + n * (1 ±0)` // Estimated: `252 + n * (1 ±0)` - // Minimum execution time: 10_438_000 picoseconds. - Weight::from_parts(12_011_610, 0) + // Minimum execution time: 10_595_000 picoseconds. + Weight::from_parts(12_202_921, 0) .saturating_add(Weight::from_parts(0, 252)) - // Standard Error: 21 - .saturating_add(Weight::from_parts(1_561, 0).saturating_mul(n.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(1_486, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -991,11 +1053,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `253 + n * (1 ±0)` // Estimated: `252 + n * (1 ±0)` - // Minimum execution time: 12_223_000 picoseconds. - Weight::from_parts(13_499_517, 0) + // Minimum execution time: 12_543_000 picoseconds. + Weight::from_parts(13_860_580, 0) .saturating_add(Weight::from_parts(0, 252)) - // Standard Error: 22 - .saturating_add(Weight::from_parts(937, 0).saturating_mul(n.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(743, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1006,11 +1068,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `253 + n * (1 ±0)` // Estimated: `252 + n * (1 ±0)` - // Minimum execution time: 14_177_000 picoseconds. - Weight::from_parts(16_024_403, 0) + // Minimum execution time: 14_605_000 picoseconds. + Weight::from_parts(16_434_846, 0) .saturating_add(Weight::from_parts(0, 252)) - // Standard Error: 29 - .saturating_add(Weight::from_parts(1_623, 0).saturating_mul(n.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(1_876, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1019,40 +1081,40 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_837_000 picoseconds. - Weight::from_parts(2_183_000, 0) + // Minimum execution time: 1_820_000 picoseconds. + Weight::from_parts(2_112_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_191_000 picoseconds. - Weight::from_parts(2_557_000, 0) + // Minimum execution time: 2_137_000 picoseconds. + Weight::from_parts(2_511_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_670_000 picoseconds. - Weight::from_parts(1_957_000, 0) + // Minimum execution time: 1_591_000 picoseconds. + Weight::from_parts(1_892_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_850_000 picoseconds. - Weight::from_parts(2_163_000, 0) + // Minimum execution time: 1_740_000 picoseconds. + Weight::from_parts(2_036_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_479_000 picoseconds. - Weight::from_parts(1_784_000, 0) + // Minimum execution time: 1_488_000 picoseconds. + Weight::from_parts(1_839_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `n` is `[0, 416]`. @@ -1061,60 +1123,60 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_667_000 picoseconds. - Weight::from_parts(3_133_527, 0) + // Minimum execution time: 2_772_000 picoseconds. + Weight::from_parts(3_112_595, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7 - .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(355, 0).saturating_mul(o.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(300, 0).saturating_mul(n.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(341, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 416]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_784_000 picoseconds. - Weight::from_parts(5_556_885, 0) + // Minimum execution time: 4_961_000 picoseconds. + Weight::from_parts(5_645_055, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(264, 0).saturating_mul(n.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_234_000 picoseconds. - Weight::from_parts(2_702_488, 0) + // Minimum execution time: 2_350_000 picoseconds. + Weight::from_parts(2_721_929, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 6 - .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(316, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_133_000 picoseconds. - Weight::from_parts(4_805_741, 0) + // Minimum execution time: 4_313_000 picoseconds. + Weight::from_parts(4_864_947, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(69, 0).saturating_mul(n.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(157, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_334_000 picoseconds. - Weight::from_parts(5_962_060, 0) + // Minimum execution time: 5_437_000 picoseconds. + Weight::from_parts(6_064_317, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) /// Storage: `Revive::AccountInfoOf` (r:1 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1124,25 +1186,23 @@ impl pallet_revive::WeightInfo for WeightInfo { /// The range of component `t` is `[0, 1]`. /// The range of component `d` is `[0, 1]`. /// The range of component `i` is `[0, 1048576]`. - fn seal_call(t: u32, d: u32, i: u32, ) -> Weight { + fn seal_call(t: u32, d: u32, _i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `3900` // Estimated: `7365` - // Minimum execution time: 111_157_000 picoseconds. - Weight::from_parts(95_709_680, 0) + // Minimum execution time: 111_031_000 picoseconds. + Weight::from_parts(98_717_384, 0) .saturating_add(Weight::from_parts(0, 7365)) - // Standard Error: 104_258 - .saturating_add(Weight::from_parts(19_356_056, 0).saturating_mul(t.into())) - // Standard Error: 104_258 - .saturating_add(Weight::from_parts(24_541_510, 0).saturating_mul(d.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) + // Standard Error: 98_721 + .saturating_add(Weight::from_parts(16_700_838, 0).saturating_mul(t.into())) + // Standard Error: 98_721 + .saturating_add(Weight::from_parts(23_443_077, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) } /// Storage: `Revive::AccountInfoOf` (r:1 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `d` is `[0, 1]`. @@ -1150,20 +1210,21 @@ impl pallet_revive::WeightInfo for WeightInfo { fn seal_call_precompile(d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + d * (1380 ±0)` - // Estimated: `2423 + d * (2423 ±0)` - // Minimum execution time: 27_182_000 picoseconds. - Weight::from_parts(11_880_237, 0) - .saturating_add(Weight::from_parts(0, 2423)) - // Standard Error: 25_689 - .saturating_add(Weight::from_parts(18_001_650, 0).saturating_mul(d.into())) + // Estimated: `3465 + d * (2423 ±0)` + // Minimum execution time: 29_696_000 picoseconds. + Weight::from_parts(13_947_315, 0) + .saturating_add(Weight::from_parts(0, 3465)) + // Standard Error: 18_595 + .saturating_add(Weight::from_parts(17_698_370, 0).saturating_mul(d.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(333, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) + .saturating_add(Weight::from_parts(332, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(d.into()))) .saturating_add(Weight::from_parts(0, 2423).saturating_mul(d.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1172,8 +1233,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `747` // Estimated: `4212` - // Minimum execution time: 36_087_000 picoseconds. - Weight::from_parts(39_046_000, 0) + // Minimum execution time: 37_187_000 picoseconds. + Weight::from_parts(39_769_000, 0) .saturating_add(Weight::from_parts(0, 4212)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -1182,7 +1243,7 @@ impl pallet_revive::WeightInfo for WeightInfo { /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Revive::AccountInfoOf` (r:1 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::NativeDepositOf` (r:1 w:0) /// Proof: `Revive::NativeDepositOf` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) @@ -1202,22 +1263,22 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `4303 + d * (818 ±53) + t * (818 ±53)` - // Minimum execution time: 207_253_000 picoseconds. - Weight::from_parts(152_890_438, 0) + // Minimum execution time: 209_102_000 picoseconds. + Weight::from_parts(154_187_795, 0) .saturating_add(Weight::from_parts(0, 4303)) - // Standard Error: 483_476 - .saturating_add(Weight::from_parts(27_454_466, 0).saturating_mul(t.into())) - // Standard Error: 483_476 - .saturating_add(Weight::from_parts(36_711_318, 0).saturating_mul(d.into())) + // Standard Error: 471_665 + .saturating_add(Weight::from_parts(27_712_623, 0).saturating_mul(t.into())) + // Standard Error: 471_665 + .saturating_add(Weight::from_parts(39_054_050, 0).saturating_mul(d.into())) // Standard Error: 5 - .saturating_add(Weight::from_parts(4_062, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_063, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) .saturating_add(Weight::from_parts(0, 818).saturating_mul(d.into())) .saturating_add(Weight::from_parts(0, 818).saturating_mul(t.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `Measured`) /// Storage: `Revive::NativeDepositOf` (r:2 w:1) /// Proof: `Revive::NativeDepositOf` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) @@ -1245,15 +1306,15 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `4102` // Estimated: `9574 + d * (405 ±35) + t * (405 ±35)` - // Minimum execution time: 442_091_000 picoseconds. - Weight::from_parts(311_313_428, 0) + // Minimum execution time: 446_873_000 picoseconds. + Weight::from_parts(297_469_948, 0) .saturating_add(Weight::from_parts(0, 9574)) - // Standard Error: 699_053 - .saturating_add(Weight::from_parts(23_613_804, 0).saturating_mul(t.into())) - // Standard Error: 699_053 - .saturating_add(Weight::from_parts(26_284_573, 0).saturating_mul(d.into())) - // Standard Error: 27 - .saturating_add(Weight::from_parts(8_185, 0).saturating_mul(i.into())) + // Standard Error: 735_140 + .saturating_add(Weight::from_parts(25_713_911, 0).saturating_mul(t.into())) + // Standard Error: 735_140 + .saturating_add(Weight::from_parts(37_564_429, 0).saturating_mul(d.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(8_439, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(Weight::from_parts(0, 405).saturating_mul(d.into())) @@ -1264,108 +1325,108 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_008_000 picoseconds. - Weight::from_parts(19_405_025, 0) + // Minimum execution time: 2_140_000 picoseconds. + Weight::from_parts(21_138_086, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_270, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_278, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn identity(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_223_000 picoseconds. - Weight::from_parts(1_500_651, 0) + // Minimum execution time: 1_277_000 picoseconds. + Weight::from_parts(1_500_613, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(115, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn ripemd_160(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_889_000 picoseconds. - Weight::from_parts(6_282_251, 0) + // Minimum execution time: 1_930_000 picoseconds. + Weight::from_parts(3_551_977, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(3_746, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_745, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_355_000 picoseconds. - Weight::from_parts(20_231_959, 0) + // Minimum execution time: 1_432_000 picoseconds. + Weight::from_parts(21_216_978, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(3_569, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_549, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_824_000 picoseconds. - Weight::from_parts(21_461_093, 0) + // Minimum execution time: 2_755_000 picoseconds. + Weight::from_parts(21_405_381, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_417, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_778_000 picoseconds. - Weight::from_parts(20_014_320, 0) + // Minimum execution time: 2_914_000 picoseconds. + Weight::from_parts(21_074_164, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_426, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_433, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048321]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 44_155_000 picoseconds. - Weight::from_parts(93_780_853, 0) + // Minimum execution time: 44_758_000 picoseconds. + Weight::from_parts(81_482_630, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 3 - .saturating_add(Weight::from_parts(5_082, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(4_985, 0).saturating_mul(n.into())) } fn ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 48_628_000 picoseconds. - Weight::from_parts(49_996_000, 0) + // Minimum execution time: 48_881_000 picoseconds. + Weight::from_parts(50_137_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn p256_verify() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_799_577_000 picoseconds. - Weight::from_parts(1_818_663_000, 0) + // Minimum execution time: 1_849_323_000 picoseconds. + Weight::from_parts(1_867_979_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn bn128_add() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 16_693_000 picoseconds. - Weight::from_parts(18_203_000, 0) + // Minimum execution time: 18_231_000 picoseconds. + Weight::from_parts(19_556_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn bn128_mul() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 980_813_000 picoseconds. - Weight::from_parts(996_587_000, 0) + // Minimum execution time: 981_310_000 picoseconds. + Weight::from_parts(996_255_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `n` is `[0, 20]`. @@ -1373,29 +1434,29 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_290_000 picoseconds. - Weight::from_parts(4_690_350_259, 0) + // Minimum execution time: 1_458_000 picoseconds. + Weight::from_parts(5_026_509_989, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 10_896_164 - .saturating_add(Weight::from_parts(6_017_239_588, 0).saturating_mul(n.into())) + // Standard Error: 11_075_075 + .saturating_add(Weight::from_parts(5_973_935_681, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1200]`. fn blake2f(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_475_000 picoseconds. - Weight::from_parts(1_861_547, 0) + // Minimum execution time: 1_483_000 picoseconds. + Weight::from_parts(1_976_386, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 2 - .saturating_add(Weight::from_parts(29_360, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(30_701, 0).saturating_mul(n.into())) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_254_000 picoseconds. - Weight::from_parts(13_641_000, 0) + // Minimum execution time: 13_416_000 picoseconds. + Weight::from_parts(13_831_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `r` is `[0, 10000]`. @@ -1403,33 +1464,33 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 800_000 picoseconds. - Weight::from_parts(1_162_085, 0) + // Minimum execution time: 747_000 picoseconds. + Weight::from_parts(963_158, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 3 - .saturating_add(Weight::from_parts(7_777, 0).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(7_825, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_935_000 picoseconds. - Weight::from_parts(53_879_142, 0) + // Minimum execution time: 14_717_000 picoseconds. + Weight::from_parts(56_623_271, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 497 - .saturating_add(Weight::from_parts(76_409, 0).saturating_mul(r.into())) + // Standard Error: 561 + .saturating_add(Weight::from_parts(100_894, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr_empty_loop(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_846_000 picoseconds. - Weight::from_parts(4_680_007, 0) + // Minimum execution time: 4_871_000 picoseconds. + Weight::from_parts(4_662_305, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 13 - .saturating_add(Weight::from_parts(38_958, 0).saturating_mul(r.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(39_001, 0).saturating_mul(r.into())) } /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1438,24 +1499,24 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `91 + n * (1 ±0)` // Estimated: `3556 + n * (1 ±0)` - // Minimum execution time: 9_639_000 picoseconds. - Weight::from_parts(9_553_343, 0) + // Minimum execution time: 12_627_000 picoseconds. + Weight::from_parts(12_906_058, 0) .saturating_add(Weight::from_parts(0, 3556)) // Standard Error: 2 - .saturating_add(Weight::from_parts(739, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(748, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: UNKNOWN KEY `0x735f040a5d490f1107ad9c56f5ca00d2060e99e5378e562537cf3bc983e17b91` (r:2 w:1) /// Proof: UNKNOWN KEY `0x735f040a5d490f1107ad9c56f5ca00d2060e99e5378e562537cf3bc983e17b91` (r:2 w:1) /// Storage: `Revive::AccountInfoOf` (r:0 w:1) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `MaxEncodedLen`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `MaxEncodedLen`) fn v1_migration_step() -> Weight { // Proof Size summary in bytes: // Measured: `448` // Estimated: `6388` - // Minimum execution time: 12_360_000 picoseconds. - Weight::from_parts(13_329_000, 0) + // Minimum execution time: 12_432_000 picoseconds. + Weight::from_parts(13_473_000, 0) .saturating_add(Weight::from_parts(0, 6388)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -1470,8 +1531,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3620` // Estimated: `6722` - // Minimum execution time: 77_246_000 picoseconds. - Weight::from_parts(81_483_000, 0) + // Minimum execution time: 75_973_000 picoseconds. + Weight::from_parts(80_440_000, 0) .saturating_add(Weight::from_parts(0, 6722)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) @@ -1486,8 +1547,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3804` // Estimated: `6196` - // Minimum execution time: 111_369_000 picoseconds. - Weight::from_parts(118_324_000, 0) + // Minimum execution time: 109_389_000 picoseconds. + Weight::from_parts(117_145_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -1500,14 +1561,14 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `564` // Estimated: `6134` - // Minimum execution time: 16_923_000 picoseconds. - Weight::from_parts(18_347_000, 0) + // Minimum execution time: 17_428_000 picoseconds. + Weight::from_parts(18_993_000, 0) .saturating_add(Weight::from_parts(0, 6134)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Revive::AccountInfoOf` (r:2 w:0) - /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `MaxEncodedLen`) + /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(268), added: 2743, mode: `MaxEncodedLen`) /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) @@ -1529,10 +1590,10 @@ impl pallet_revive::WeightInfo for WeightInfo { fn v4_contract_step() -> Weight { // Proof Size summary in bytes: // Measured: `6302` - // Estimated: `6434` - // Minimum execution time: 176_417_000 picoseconds. - Weight::from_parts(185_418_000, 0) - .saturating_add(Weight::from_parts(0, 6434)) + // Estimated: `6476` + // Minimum execution time: 176_057_000 picoseconds. + Weight::from_parts(189_882_000, 0) + .saturating_add(Weight::from_parts(0, 6476)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -1542,8 +1603,8 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `379` // Estimated: `6288` - // Minimum execution time: 10_491_000 picoseconds. - Weight::from_parts(11_382_000, 0) + // Minimum execution time: 10_857_000 picoseconds. + Weight::from_parts(11_852_000, 0) .saturating_add(Weight::from_parts(0, 6288)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -1565,11 +1626,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3004 + n * (97 ±0)` // Estimated: `6296 + n * (104 ±1)` - // Minimum execution time: 27_532_000 picoseconds. - Weight::from_parts(56_754_578, 0) + // Minimum execution time: 28_573_000 picoseconds. + Weight::from_parts(57_897_976, 0) .saturating_add(Weight::from_parts(0, 6296)) - // Standard Error: 5_418 - .saturating_add(Weight::from_parts(533_988, 0).saturating_mul(n.into())) + // Standard Error: 5_804 + .saturating_add(Weight::from_parts(578_653, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) .saturating_add(Weight::from_parts(0, 104).saturating_mul(n.into())) @@ -1591,11 +1652,11 @@ impl pallet_revive::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3570 + d * (3 ±0)` // Estimated: `7029 + d * (3 ±0)` - // Minimum execution time: 60_079_000 picoseconds. - Weight::from_parts(62_616_634, 0) + // Minimum execution time: 60_265_000 picoseconds. + Weight::from_parts(64_189_635, 0) .saturating_add(Weight::from_parts(0, 7029)) - // Standard Error: 77 - .saturating_add(Weight::from_parts(11_715, 0).saturating_mul(d.into())) + // Standard Error: 111 + .saturating_add(Weight::from_parts(11_948, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) .saturating_add(Weight::from_parts(0, 3).saturating_mul(d.into())) @@ -1615,15 +1676,13 @@ impl pallet_revive::WeightInfo for WeightInfo { /// Storage: `Revive::ReceiptInfoData` (r:0 w:1) /// Proof: `Revive::ReceiptInfoData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `e` is `[0, 100]`. - fn on_finalize_per_event(e: u32, ) -> Weight { + fn on_finalize_per_event(_e: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `2215` // Estimated: `5680` - // Minimum execution time: 49_977_000 picoseconds. - Weight::from_parts(52_879_441, 0) + // Minimum execution time: 50_388_000 picoseconds. + Weight::from_parts(53_140_553, 0) .saturating_add(Weight::from_parts(0, 5680)) - // Standard Error: 355 - .saturating_add(Weight::from_parts(913, 0).saturating_mul(e.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -1642,15 +1701,13 @@ impl pallet_revive::WeightInfo for WeightInfo { /// Storage: `Revive::ReceiptInfoData` (r:0 w:1) /// Proof: `Revive::ReceiptInfoData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `d` is `[0, 16384]`. - fn on_finalize_per_event_data(d: u32, ) -> Weight { + fn on_finalize_per_event_data(_d: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `2215` // Estimated: `5680` - // Minimum execution time: 50_057_000 picoseconds. - Weight::from_parts(52_869_889, 0) + // Minimum execution time: 50_217_000 picoseconds. + Weight::from_parts(53_145_556, 0) .saturating_add(Weight::from_parts(0, 5680)) - // Standard Error: 2 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } diff --git a/prdoc/pr_12229.prdoc b/prdoc/pr_12229.prdoc new file mode 100644 index 0000000000000..4c674df10881b --- /dev/null +++ b/prdoc/pr_12229.prdoc @@ -0,0 +1,94 @@ +title: '[pallet-revive] EIP-7702 (continued)' +doc: +- audience: Runtime Dev + description: "Continuation of #10851.\nevm-test-suite PR: https://github.com/paritytech/evm-test-suite/pull/141\n\ + \nThis PR implements [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) (\"Set\ + \ EOA Account Code\") for `pallet-revive`, enabling Externally Owned Accounts\ + \ (EOAs) to designate a contract whose code will be executed on their behalf when\ + \ they are called.\n\n## What is EIP-7702?\n\nEIP-7702 lets an EOA sign an authorization\ + \ that says \"when someone calls me, execute contract X's code\". When a transaction\ + \ includes these authorizations, any subsequent calls to the delegated EOA will\ + \ execute the target contract's code \u2014 but using the EOA's own storage and\ + \ balance. This enables smart account patterns (batching, gas sponsorship, etc.)\ + \ without requiring [account abstraction (ERC-4337)](https://eips.ethereum.org/EIPS/eip-4337)\ + \ or proxy contracts.\n\nFor background:\n- [EIP-7702 spec](https://eips.ethereum.org/EIPS/eip-7702)\n\ + - [Vitalik's \"Why we need wide adoption of account abstraction\"](https://vitalik.eth.limo/general/2023/06/09/three_transitions.html)\n\ + - [EIP-7702 overview (ethereum.org)](https://ethereum.org/en/roadmap/account-abstraction/#eip-7702)\n\ + \n## Changes\n\n### Pallet integration\n\nThe `eth_call` dispatchable now accepts\ + \ and processes an authorization list before executing the call.\n\nThe `authorization_list`\ + \ parameter is a `BoundedVec` capped at `limits::AUTHORIZATION_LIST_MAX` (256).\ + \ In normal operation the size is already constrained by the gas-validation logic\ + \ in `eth_transact` (each entry costs at least `worst_case_delegation_deposit`),\ + \ but the `BoundedVec` is a defense-in-depth bound for any future call path that\ + \ might bypass that check. Oversized lists are rejected in `into_call` with `InvalidTransaction::Call`.\n\ + \n> **Note**: Updating the `eth_call` signature is safe \u2014 it is not dispatched\ + \ directly by users but is the inner call of `eth_transact`, which is signed by\ + \ an Ethereum wallet and submitted via `eth-rpc`.\n\n### Authorization processing\n\ + \nThe `Transaction7702` type (with its `authorization_list`) was already defined\ + \ and parsed; this PR adds the actual processing logic.\n\n`process_authorizations`\ + \ validates each entry (chain ID, signature, nonce, account type), creates the\ + \ authority account if needed, and sets or clears the delegation. Invalid authorizations\ + \ are silently skipped per spec.\n\n**Key semantics:**\n- **Authorizations always\ + \ succeed.** A type-4 transaction is only valid if the origin has enough gas to\ + \ cover the worst case for every authorization entry. Processing itself cannot\ + \ fail.\n- **Authorizations are not reverted.** If the transaction's call fails\ + \ and its state changes revert, the delegation changes from authorization processing\ + \ persist.\n\n**Revive-specific complexity.** In Revive the per-authorization\ + \ pre-dispatch weight/gas is higher than in EVM because:\n- An authorization can\ + \ touch a **new account**, so the gas must cover the existential deposit (ED)\ + \ for account creation.\n- Code is shared across contracts (same blob for init\ + \ and runtime), so delegating to a contract requires a **code lockup deposit**\ + \ and increments a **refcount** on the code hash to prevent deletion while delegated.\n\ + \n### Storage changes\n\nA new **`AccountType::DelegatedEOA`** variant is introduced:\n\ + - `delegate_target: Option` \u2014 the contract address this EOA delegates\ + \ to.\n- `contract_info: ContractInfo` \u2014 storage accounting (child trie,\ + \ base deposit) for delegated EOAs.\n\n**Key behaviors:**\n- Once delegated, an\ + \ account transitions from `EOA` to `DelegatedEOA` permanently.\n- Clearing delegation\ + \ sets `delegate_target = None` but the account stays `DelegatedEOA`.\n- Delegated\ + \ EOAs get their own child trie for storage, keyed by the EOA's address (not the\ + \ target).\n- Re-delegating to a different target preserves the child trie \u2014\ + \ storage survives across target changes.\n- Code refcounts are managed: setting\ + \ delegation increments the target's code refcount, clearing it decrements.\n\n\ + **Deposit lifecycle.** Clearing a delegation refunds the `storage_base_deposit`\ + \ (the code-lockup portion) but preserves the child trie and per-item storage\ + \ accounting. To recover the per-item deposit, the user must re-delegate to a\ + \ contract that lets them clear the stored items through the normal storage path.\n\ + \n### Execution changes\n\nDuring a call, the runtime resolves delegation:\n-\ + \ If the callee is an EOA with a `delegate_target`, code is loaded from the target\ + \ contract but execution uses the EOA's storage.\n- Constructors skip delegation\ + \ lookup (cannot deploy via delegation).\n- **No chain following.** Delegation\ + \ is resolved at most once. If A delegates to B and B delegates to C, calling\ + \ A looks up code at B directly (without following B's delegation to C). If B\ + \ is an EOA, calling A behaves like calling a plain EOA.\n\n### Benchmarks\n\n\ + - `process_new_account_authorization(n)` \u2014 worst case: creates `n` new accounts\ + \ with delegations.\n- `process_existing_account_authorization(n)` \u2014 best\ + \ case: processes `n` authorizations for existing accounts (used for weight-refund\ + \ calculation).\n\n### RPC integration\n\n- `eth-rpc` supports submitting and\ + \ dry-running EIP-7702 transactions.\n- `eth_getCode` returns the `0xef0100 ||\ + \ address` prefix for delegated EOAs (per EIP-7702 spec).\n\n## Tests\n\nTest\ + \ coverage includes:\n- **Validation**: invalid chain_id, nonce mismatch, duplicate\ + \ signers, chain_id 0 wildcard, contract accounts rejected.\n- **Delegation lifecycle**:\ + \ set, clear, re-delegate, delegation resolution during calls.\n- **Storage persistence**:\ + \ re-delegation preserves storage across different targets; clear-then-redelegate\ + \ preserves the trie and recovers read/write access to previously written state\ + \ (`storage_persists_across_clear_and_redelegate`).\n- **Deposit accounting**:\ + \ delegation charges, clearing refunds, re-delegation adjustments, code-refcount\ + \ tracking, EOA \u2192 EOA delegations charge no deposit, multiple authorities\ + \ to the same target each get their own deposit.\n- **Edge cases**: delegation\ + \ chains not followed, SELFDESTRUCT preserves delegation, constructors skip delegation,\ + \ delegation to nonexistent addresses is a no-op." +crates: +- name: pallet-revive-eth-rpc + bump: major +- name: pallet-revive + bump: major +- name: assets-common + bump: major +- name: pallet-xcm-precompiles + bump: major +- name: pallet-assets-precompiles + bump: major +- name: pallet-revive-fixtures + bump: major +- name: asset-hub-westend-runtime + bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index a134e334db472..b171757dc96f6 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -71,6 +71,7 @@ alloy-consensus = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } itertools = { workspace = true } +k256 = { features = ["alloc", "ecdsa"], workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } @@ -104,6 +105,7 @@ std = [ "frame-support/std", "frame-system/std", "humantime-serde", + "k256", "k256?/std", "log/std", "num-bigint/std", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 896c9bf7969cd..238f9bebb0ba1 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -65,6 +65,7 @@ alloy-rpc-types = { workspace = true, default-features = true, features = ["trac alloy-signer-local = { workspace = true, default-features = true } env_logger = { workspace = true } frame-system = { workspace = true, default-features = true } +k256 = { features = ["alloc", "ecdsa"], workspace = true } pallet-revive-fixtures = { workspace = true, default-features = true } pretty_assertions = { workspace = true } revive-dev-node = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 1ae77e887ec4f..3cc23fd151c8a 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -27,6 +27,7 @@ pub enum TransactionType { Eip2930, Eip1559, Eip4844, + Eip7702, } /// Transaction builder. @@ -37,6 +38,7 @@ pub struct TransactionBuilder { input: Bytes, to: Option, nonce: Option, + authorization_list: Vec, gas: Option, mutate: Box, } @@ -49,6 +51,11 @@ pub struct SubmittedTransaction { } impl SubmittedTransaction { + /// Create from a raw transaction hash and gas limit. + pub fn from_raw(client: Arc, hash: H256, gas: U256) -> Self { + Self { tx: GenericTransaction { gas: Some(gas), ..Default::default() }, hash, client } + } + /// Get the hash of the transaction. pub fn hash(&self) -> H256 { self.hash @@ -101,6 +108,7 @@ impl TransactionBuilder { input: Bytes::default(), to: None, nonce: None, + authorization_list: vec![], gas: None, mutate: Box::new(|_| {}), } @@ -135,6 +143,12 @@ impl TransactionBuilder { self } + /// Set the authorization list (EIP-7702). + pub fn authorization_list(mut self, list: Vec) -> Self { + self.authorization_list = list; + self + } + /// Set the gas limit explicitly, skipping eth_estimateGas. pub fn gas(mut self, gas: U256) -> Self { self.gas = Some(gas); @@ -149,7 +163,7 @@ impl TransactionBuilder { /// Call eth_call to get the result of a view function pub async fn eth_call(self) -> anyhow::Result> { - let TransactionBuilder { client, signer, value, input, to, .. } = self; + let TransactionBuilder { client, signer, value, input, to, authorization_list, .. } = self; let from = signer.address(); let result = client @@ -159,6 +173,7 @@ impl TransactionBuilder { input: input.into(), value: Some(value), to, + authorization_list, ..Default::default() }, None, @@ -179,7 +194,17 @@ impl TransactionBuilder { self, tx_type: TransactionType, ) -> anyhow::Result> { - let TransactionBuilder { client, signer, value, input, to, nonce, gas, mutate } = self; + let TransactionBuilder { + client, + signer, + value, + input, + to, + nonce, + authorization_list, + gas, + mutate, + } = self; let from = signer.address(); let chain_id = client.chain_id().await?; @@ -193,6 +218,13 @@ impl TransactionBuilder { .with_context(|| "Failed to fetch account nonce")? }; + let r#type = match tx_type { + TransactionType::Legacy => None, + TransactionType::Eip2930 => Some(Byte(1)), + TransactionType::Eip1559 => Some(Byte(2)), + TransactionType::Eip4844 => Some(Byte(3)), + TransactionType::Eip7702 => Some(Byte(4)), + }; let gas = if let Some(gas) = gas { gas } else { @@ -204,6 +236,8 @@ impl TransactionBuilder { value: Some(value), gas_price: Some(gas_price), to, + r#type, + authorization_list: authorization_list.clone(), ..Default::default() }, None, @@ -275,6 +309,23 @@ impl TransactionBuilder { } .into() }, + TransactionType::Eip7702 => { + let to = to.ok_or_else(|| { + anyhow::anyhow!("EIP-7702 transactions require a destination address") + })?; + Transaction7702Unsigned { + gas, + nonce, + to, + value, + input, + max_fee_per_gas: gas_price, + chain_id, + authorization_list, + ..Default::default() + } + .into() + }, }; mutate(&mut unsigned_tx); diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 67a71ebaea071..4138513b0eed5 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -359,6 +359,7 @@ async fn run_all_eth_rpc_tests_inner() -> anyhow::Result<()> { test_multiple_transactions_in_block, test_mixed_evm_substrate_transactions, test_runtime_pallets_address_upload_code, + test_eip7702_delegation_flow, test_subscribe_new_heads, test_subscribe_new_heads_multiple_blocks, test_subscribe_logs, @@ -1126,6 +1127,123 @@ async fn test_runtime_pallets_address_upload_code() -> anyhow::Result<()> { Ok(()) } +/// Full EIP-7702 integration test: +/// 1. Deploy Counter contract +/// 2. Delegate Alice → Counter via 7702 tx with authorization list +/// 3. Call setNumber(42) on Alice (writes to Alice's storage via Counter code) +/// 4. Read number() from Alice → returns 42 +/// 5. Clear delegation (authorization with zero address) +/// 6. Read from Alice → returns empty (no code) +async fn test_eip7702_delegation_flow() -> anyhow::Result<()> { + use crate::example::TransactionType; + use pallet_revive::{evm::Account, precompiles::alloy::sol_types::SolCall}; + use pallet_revive_fixtures::Counter; + + let client = Arc::new(SharedResources::client().await); + let alith = Account::default(); + + // Deploy Counter contract + let (counter_code, _) = pallet_revive_fixtures::compile_module_with_type( + "Counter", + pallet_revive_fixtures::FixtureType::Solc, + )?; + let nonce = client.get_transaction_count(alith.address(), BlockTag::Latest.into()).await?; + let tx = TransactionBuilder::new(client.clone()) + .input(counter_code.to_vec()) + .send() + .await?; + tx.wait_for_receipt().await?; + let counter_addr = create1(&alith.address(), nonce.try_into().unwrap()); + + // Create authority account with known seed + let seed = [0xAA; 32]; + let authority = Account::from_secret_key(seed); + let authority_key = k256::ecdsa::SigningKey::from_bytes(&seed.into()).unwrap(); + + // Fund the authority + let tx = TransactionBuilder::new(client.clone()) + .value(U256::from(10_000_000_000_000_000_000u128)) + .to(authority.address()) + .send() + .await?; + tx.wait_for_receipt().await?; + + let chain_id = client.chain_id().await?; + + // --- Step 1: Delegate authority → Counter via 7702 tx --- + let auth_nonce = client + .get_transaction_count(authority.address(), BlockTag::Latest.into()) + .await?; + let auth = pallet_revive::evm::eip7702::sign_authorization( + &authority_key, + chain_id, + counter_addr, + auth_nonce, + ); + TransactionBuilder::new(client.clone()) + .to(alith.address()) + .authorization_list(vec![auth]) + .send_with_type(TransactionType::Eip7702) + .await? + .wait_for_receipt() + .await?; + + // Verify delegation is active: eth_getCode should return the delegation indicator + let code = client.get_code(authority.address(), BlockTag::Latest.into()).await?; + let mut expected_prefix = vec![0xef, 0x01, 0x00]; + expected_prefix.extend_from_slice(counter_addr.as_bytes()); + assert_eq!(code.0, expected_prefix, "authority should have delegation indicator code"); + + // --- Step 2: Call setNumber(42) on the authority address --- + let tx = TransactionBuilder::new(client.clone()) + .to(authority.address()) + .input(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .send() + .await?; + tx.wait_for_receipt().await?; + + // --- Step 3: Read number() from authority → should return 42 --- + let result = TransactionBuilder::new(client.clone()) + .to(authority.address()) + .input(Counter::numberCall {}.abi_encode()) + .eth_call() + .await?; + let number = Counter::numberCall::abi_decode_returns(&result).unwrap(); + assert_eq!(number, 42u64, "number() should return 42 after setNumber"); + + // --- Step 4: Clear delegation via 7702 tx with zero address --- + let auth_nonce = client + .get_transaction_count(authority.address(), BlockTag::Latest.into()) + .await?; + let clear_auth = pallet_revive::evm::eip7702::sign_authorization( + &authority_key, + chain_id, + pallet_revive::evm::Address::zero(), + auth_nonce, + ); + TransactionBuilder::new(client.clone()) + .to(alith.address()) + .authorization_list(vec![clear_auth]) + .send_with_type(TransactionType::Eip7702) + .await? + .wait_for_receipt() + .await?; + + // --- Step 5: Verify delegation is cleared --- + let code = client.get_code(authority.address(), BlockTag::Latest.into()).await?; + assert!(code.0.is_empty(), "authority should have no code after clearing delegation"); + + // Calling number() should return empty (no contract code) + let result = TransactionBuilder::new(client.clone()) + .to(authority.address()) + .input(Counter::numberCall {}.abi_encode()) + .eth_call() + .await?; + assert!(result.is_empty(), "call to cleared delegation should return empty data"); + + Ok(()) +} + /// Verify that subscribing to `newHeads` delivers a block header matching the /// corresponding block fetched via `eth_getBlockByNumber` after a transaction /// triggers a new block. diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 7e88550a61781..6b046e0110b6e 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -126,6 +126,88 @@ mod benchmarks { } } + // Benchmark for processing N EIP-7702 authorizations with empty accounts + // This measures the overhead of processing the authorization list + // Parameter `n`: number of authorizations to process + #[benchmark(pov_mode = Measured)] + fn process_new_account_authorization(n: Linear<0, 255>) -> Result<(), BenchmarkError> { + use crate::evm::eip7702; + use sp_io::hashing::keccak_256; + + let chain_id = U256::from(T::ChainId::get()); + // Delegate to a deployed contract so ContractInfo is created (worst case) + let target_contract = Contract::::with_index(0, VmBinaryModule::dummy(), vec![])?; + let target = target_contract.address; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + ::FeeInfo::deposit_txfee( + ::Currency::issue(caller_funding::()), + ); + let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX); + + let mut authorization_list = vec![]; + for i in 0..n { + let key_material = keccak_256(&(i as u32).to_le_bytes()); + let key = SigningKey::from_bytes(&key_material.into()).expect("valid key; qed"); + let signed_auth = eip7702::sign_authorization(&key, chain_id, target, U256::zero()); + authorization_list.push(signed_auth); + } + + let auth_result; + #[block] + { + auth_result = + eip7702::process_authorizations::(&authorization_list, &caller, &exec_config) + .expect("should succeed"); + } + + assert_eq!(auth_result.new_accounts, n as u32, "All authorizations should be new"); + Ok(()) + } + + // Benchmark for processing N EIP-7702 authorizations with existing accounts + // Parameter `n`: number of authorizations to process + #[benchmark(pov_mode = Measured)] + fn process_existing_account_authorization(n: Linear<0, 255>) -> Result<(), BenchmarkError> { + use crate::evm::eip7702; + use sp_io::hashing::keccak_256; + + let chain_id = U256::from(T::ChainId::get()); + // Delegate to a deployed contract so ContractInfo is created (worst case) + let target_contract = Contract::::with_index(0, VmBinaryModule::dummy(), vec![])?; + let target = target_contract.address; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + ::FeeInfo::deposit_txfee( + ::Currency::issue(caller_funding::()), + ); + let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX); + + let mut authorization_list = vec![]; + for i in 0..n { + let key_material = keccak_256(&(i as u32).to_le_bytes()); + let key = SigningKey::from_bytes(&key_material.into()).expect("valid key; qed"); + + let eth_address = eip7702::eth_address(&key); + let account_id = T::AddressMapper::to_account_id(ð_address); + T::Currency::set_balance(&account_id, Pallet::::min_balance()); + + let signed_auth = eip7702::sign_authorization(&key, chain_id, target, U256::zero()); + authorization_list.push(signed_auth); + } + + let auth_result; + #[block] + { + auth_result = + eip7702::process_authorizations::(&authorization_list, &caller, &exec_config) + .expect("should succeed"); + } + + assert_eq!(auth_result.new_accounts, 0u32); + Ok(()) + } + /// Measures the per-entry cost of `process_deletion_queue_batch`: one `DeletionQueue` read /// plus the `DeletionQueue` + `DeletionQueueCounter` writes done by `entry.remove()`. #[benchmark(pov_mode = Measured)] @@ -518,6 +600,7 @@ mod benchmarks { TransactionSigned::default().signed_payload(), effective_gas_price, 0, + vec![], ); // contract should have received the value diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index 6b76af86b516e..7a1d42e79135e 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -28,6 +28,9 @@ pub mod runtime; pub mod tx_extension; pub use alloy_core::sol_types::decode_revert_reason; +/// EIP-7702: Set EOA Account Code +pub mod eip7702; + /// Ethereum block hash builder related types. pub(crate) mod block_hash; pub use block_hash::ReceiptGasInfo; diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index 9b045c01d7aa7..600ba58362d0d 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -43,3 +43,4 @@ mod account; pub use account::*; mod signature; +pub use signature::recover_eth_address_from_message; diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 89c7ecd055bc4..db750acb8b1b7 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -237,6 +237,17 @@ impl Decodable for AuthorizationListEntry { } } +impl AuthorizationListEntry { + /// RLP encode only the unsigned part (chain_id, address, nonce) for signing + pub fn rlp_encode_unsigned(&self) -> Vec { + let mut s = rlp::RlpStream::new_list(3); + s.append(&self.chain_id); + s.append(&self.address); + s.append(&self.nonce); + s.out().to_vec() + } +} + /// See impl Encodable for Transaction1559Unsigned { fn rlp_append(&self, s: &mut rlp::RlpStream) { diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index d4bc1d5c257fe..6ec139b1c0e72 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -913,6 +913,20 @@ pub struct AuthorizationListEntry { pub s: U256, } +impl AuthorizationListEntry { + /// Convert signature components (r, s, y_parity) into a 65-byte ECDSA signature. + pub fn signature(&self) -> [u8; 65] { + let mut signature = [0u8; 65]; + let r_bytes = self.r.to_big_endian(); + let s_bytes = self.s.to_big_endian(); + signature[..32].copy_from_slice(&r_bytes); + signature[32..64].copy_from_slice(&s_bytes); + debug_assert!(self.y_parity <= U256::from(1u8), "y_parity should be 0 or 1"); + signature[64] = self.y_parity.low_u32() as u8; + signature + } +} + #[derive( Debug, Clone, diff --git a/substrate/frame/revive/src/evm/api/signature.rs b/substrate/frame/revive/src/evm/api/signature.rs index 9d148fd5fe02a..03e0bab1043a1 100644 --- a/substrate/frame/revive/src/evm/api/signature.rs +++ b/substrate/frame/revive/src/evm/api/signature.rs @@ -20,6 +20,22 @@ use super::*; use sp_core::{H160, U256}; use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +/// Recover an Ethereum address from a message hash and signature. +/// +/// # Parameters +/// - `message`: The message bytes to hash +/// - `signature`: The 65-byte ECDSA signature (r, s, v) +/// +/// # Returns +/// The recovered Ethereum address, or an error if recovery fails +pub fn recover_eth_address_from_message(message: &[u8], signature: &[u8; 65]) -> Result { + let hash = keccak_256(message); + let pk = secp256k1_ecdsa_recover(signature, &hash).map_err(|_| ())?; + let mut addr = H160::default(); + addr.assign_from_slice(&keccak_256(&pk[..])[12..]); + Ok(addr) +} + impl TransactionLegacySigned { /// Get the recovery ID from the signed transaction. /// See https://eips.ethereum.org/EIPS/eip-155 @@ -174,11 +190,7 @@ impl TransactionSigned { let bytes = s.out().to_vec(); let signature = self.raw_signature()?; - let hash = keccak_256(&bytes); - let mut addr = H160::default(); - let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?; - addr.assign_from_slice(&keccak_256(&pk[..])[12..]); - Ok(addr) + recover_eth_address_from_message(&bytes, &signature) } } diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index 0a3db8a366737..760428145315e 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -21,17 +21,18 @@ use crate::{ BalanceOf, CallOf, Config, GenericTransaction, LOG_TARGET, Pallet, RUNTIME_PALLETS_ADDR, Weight, Zero, evm::{ - TYPE_LEGACY, + TYPE_EIP7702, TYPE_LEGACY, fees::{InfoT, compute_max_integer_quotient}, runtime::SetWeightLimit, }, extract_code_and_data, + metering::EthTxInfo, }; use alloc::{boxed::Box, vec::Vec}; use codec::DecodeLimit; use frame_support::MAX_EXTRINSIC_DEPTH; use sp_core::{Get, U256}; -use sp_runtime::{SaturatedConversion, transaction_validity::InvalidTransaction}; +use sp_runtime::{SaturatedConversion, Saturating, transaction_validity::InvalidTransaction}; /// Result of decoding an eth transaction into a dispatchable call. pub struct CallInfo { @@ -49,6 +50,8 @@ pub struct CallInfo { pub storage_deposit: BalanceOf, /// The ethereum gas limit of the transaction. pub eth_gas_limit: U256, + /// EIP-7702: List of authorization tuples to process + pub authorization_list: Vec, } /// Mode for creating a call from an ethereum transaction. @@ -98,6 +101,20 @@ impl GenericTransaction { return Err(InvalidTransaction::Call); }; + // EIP-7702: Validate that type 0x04 transactions have a non-null destination + if let Some(super::Byte(TYPE_EIP7702)) = self.r#type.as_ref() { + if self.to.is_none() { + log::debug!(target: LOG_TARGET, "EIP-7702 transactions require non-null destination"); + return Err(InvalidTransaction::Call); + } + + // EIP-7702: Validate that type 0x04 transactions have non-empty authorization list + if self.authorization_list.is_empty() { + log::debug!(target: LOG_TARGET, "EIP-7702 transactions require non-empty authorization list"); + return Err(InvalidTransaction::Call); + } + } + // Currently, effective_gas_price will always be the same as base_fee // Because all callers of `into_call` will prepare `tx` that way. Some of the subsequent // logic will not work correctly anymore if we change that assumption. @@ -124,6 +141,7 @@ impl GenericTransaction { let maximized_base_fee = base_fee.saturating_mul(256.into()); maximized_tx.gas = Some(u64::MAX.into()); maximized_tx.gas_price = Some(maximized_base_fee); + maximized_tx.max_fee_per_gas = Some(maximized_base_fee); maximized_tx.max_priority_fee_per_gas = Some(maximized_base_fee); let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| { @@ -157,7 +175,7 @@ impl GenericTransaction { crate::Call::eth_substrate_call:: { call: Box::new(call), transaction_encoded } .into() } else { - let call = crate::Call::eth_call:: { + crate::Call::eth_call:: { dest, value, weight_limit: Zero::zero(), @@ -166,9 +184,9 @@ impl GenericTransaction { transaction_encoded, effective_gas_price, encoded_len, + authorization_list: self.authorization_list.clone(), } - .into(); - call + .into() } } else { let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { @@ -247,6 +265,26 @@ impl GenericTransaction { InvalidTransaction::Payment })?.saturated_into(); + // EIP-7702: Ensure enough gas to cover worst-case deposits for authorizations + // (ED + contract storage deposit + code lockup per authorization). + if !self.authorization_list.is_empty() { + let info = ::FeeInfo::base_dispatch_info(&mut call); + let max_deposit = EthTxInfo::::new(encoded_len, info.total_weight()) + .max_deposit(gas.saturated_into()); + + let auth_cost = >::worst_case_delegation_deposit() + .saturating_mul(self.authorization_list.len().saturated_into()); + + if max_deposit < auth_cost { + log::debug!( + target: LOG_TARGET, + "Not enough gas to cover deposits for authorization accounts. \ + max_deposit={max_deposit:?} required={auth_cost:?}" + ); + return Err(InvalidTransaction::Payment); + } + } + Ok(CallInfo { call, weight_limit, @@ -254,6 +292,7 @@ impl GenericTransaction { tx_fee, storage_deposit, eth_gas_limit: gas, + authorization_list: self.authorization_list, }) } } diff --git a/substrate/frame/revive/src/evm/eip7702.rs b/substrate/frame/revive/src/evm/eip7702.rs new file mode 100644 index 0000000000000..626c05480fd84 --- /dev/null +++ b/substrate/frame/revive/src/evm/eip7702.rs @@ -0,0 +1,225 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! EIP-7702: Set EOA Account Code implementation +//! +//! This module implements the authorization processing for EIP-7702, which allows +//! Externally Owned Accounts (EOAs) to temporarily set code in their account via +//! authorization tuples attached to transactions. + +use crate::{ + BalanceOf, Config, Error, ExecConfig, HoldReason, LOG_TARGET, Pallet, RuntimeCosts, + address::AddressMapper, + evm::{ + api::{AuthorizationListEntry, recover_eth_address_from_message}, + fees::InfoT as _, + }, + metering, + primitives::StorageDeposit, + storage::AccountInfo, +}; +use alloc::vec::Vec; +use frame_support::{ + traits::fungible::{Balanced as _, Inspect}, + weights::Weight, +}; +use sp_core::{Get, H160, U256}; +use sp_runtime::{SaturatedConversion, Saturating}; + +/// EIP-7702: Magic value for authorization signature message +const EIP7702_MAGIC: u8 = 0x05; + +/// Result of processing EIP-7702 authorization tuples. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct AuthorizationResult { + /// Number of authorizations that created new accounts. + pub new_accounts: u32, + /// Number of authorizations that applied to existing accounts. + pub existing_accounts: u32, + /// Total deposit charged from the origin during authorization processing. + pub deposit: Balance, + /// Weight to refund for authorizations that hit existing accounts. + pub weight_refund: Weight, +} + +/// Process a list of EIP-7702 authorization tuples. +/// +/// For new accounts the ED is charged from `origin` via [`Pallet::charge_deposit`]. +/// The pre-dispatch weight assumes all authorizations create new accounts (worst case). +/// The returned `weight_refund` accounts for authorizations that hit existing accounts. +/// +/// Note: We process authorizations OUTSIDE the transaction context so delegation changes persist +/// even if the call fails. +pub fn process_authorizations( + authorization_list: &[AuthorizationListEntry], + origin: &T::AccountId, + exec_config: &ExecConfig, +) -> Result>, sp_runtime::DispatchError> { + let chain_id = U256::from(T::ChainId::get()); + let ed = >::minimum_balance(); + let mut result: AuthorizationResult> = Default::default(); + + for auth in authorization_list.iter() { + if !auth.chain_id.is_zero() && auth.chain_id != chain_id { + log::debug!(target: LOG_TARGET, "Invalid chain_id in authorization: expected {chain_id:?} or 0, got {:?}", auth.chain_id); + continue; + } + + let Ok(authority) = recover_authority(auth) else { + log::debug!(target: LOG_TARGET, "Failed to recover authority from signature"); + continue; + }; + let account_id = T::AddressMapper::to_account_id(&authority); + + let current_nonce: u64 = + frame_system::Pallet::::account_nonce(&account_id).saturated_into(); + let Ok::(expected_nonce) = auth.nonce.try_into() else { + log::debug!(target: LOG_TARGET, "Authorization nonce too large: {:?}", auth.nonce); + continue; + }; + + if current_nonce != expected_nonce { + log::debug!(target: LOG_TARGET, "Nonce mismatch for {authority:?}: expected {expected_nonce:?}, got {current_nonce:?}"); + continue; + } + + if AccountInfo::::is_contract(&authority) { + log::debug!(target: LOG_TARGET, "Account {authority:?} has non-delegation code"); + continue; + } + + let account_exists = frame_system::Account::::contains_key(&account_id); + if auth.address.is_zero() && !account_exists { + log::debug!(target: LOG_TARGET, "Skipping clear delegation for non-existent account {authority:?}"); + continue; + } + + if !account_exists { + // Transfer ED to the new authority account without placing a hold: + // the ED must remain as transferable balance so the account exists. + // Funded from the tx fee pool (process_authorizations only runs from + // eth-tx contexts). + let credit = ::FeeInfo::withdraw_txfee(ed) + .ok_or(Error::::StorageDepositNotEnoughFunds)?; + ::Currency::resolve(&account_id, credit) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + result.deposit.saturating_accrue(ed); + result.new_accounts += 1; + } else { + result.existing_accounts += 1; + } + + // Apply delegation + let deposit = if auth.address.is_zero() { + AccountInfo::::clear_delegation(&authority) + } else { + AccountInfo::::set_delegation(&authority, auth.address) + }; + + let Ok(deposit) = deposit else { + log::debug!(target: LOG_TARGET, "Delegation failed for {authority:?}, skipping"); + continue; + }; + + match deposit { + StorageDeposit::Charge(amount) => { + Pallet::::charge_deposit( + HoldReason::StorageDepositReserve, + origin, + &account_id, + amount, + exec_config, + )?; + result.deposit.saturating_accrue(amount); + }, + StorageDeposit::Refund(amount) => { + Pallet::::refund_deposit( + HoldReason::StorageDepositReserve, + &account_id, + exec_config.funds(origin), + amount, + )?; + result.deposit = result.deposit.saturating_sub(amount); + }, + } + + frame_system::Pallet::::inc_account_nonce(&account_id); + } + + let worst_case_weight = + >::weight(&RuntimeCosts::Delegations { + new_accounts: authorization_list.len() as u32, + existing_accounts: 0, + }); + let actual_weight = >::weight(&RuntimeCosts::Delegations { + new_accounts: result.new_accounts, + existing_accounts: result.existing_accounts, + }); + result.weight_refund = worst_case_weight.saturating_sub(actual_weight); + + Ok(result) +} + +/// Build the EIP-7702 signing message: `MAGIC || rlp([chain_id, address, nonce])` +fn signing_message(auth: &AuthorizationListEntry) -> Vec { + let mut message = Vec::with_capacity(1 + 64); + message.push(EIP7702_MAGIC); + message.extend_from_slice(&auth.rlp_encode_unsigned()); + message +} + +/// Recover the authority address from an authorization signature +fn recover_authority(auth: &AuthorizationListEntry) -> Result { + recover_eth_address_from_message(&signing_message(auth), &auth.signature()) +} + +/// Sign an authorization entry +/// +/// This is a helper function for benchmarks and tests. +#[cfg(any(feature = "std", feature = "runtime-benchmarks"))] +pub fn sign_authorization( + key: &k256::ecdsa::SigningKey, + chain_id: U256, + address: H160, + nonce: U256, +) -> AuthorizationListEntry { + let unsigned = AuthorizationListEntry { chain_id, address, nonce, ..Default::default() }; + let hash = sp_io::hashing::keccak_256(&signing_message(&unsigned)); + let (signature, recovery_id) = + key.sign_prehash_recoverable(&hash).expect("signing success; qed"); + + let sig_bytes = signature.to_bytes(); + AuthorizationListEntry { + chain_id, + address, + nonce, + y_parity: U256::from(recovery_id.to_byte()), + r: U256::from_big_endian(&sig_bytes[..32]), + s: U256::from_big_endian(&sig_bytes[32..64]), + } +} + +/// Derive the Ethereum address from a signing key. +/// +/// This is a helper function for benchmarks and tests. +#[cfg(any(feature = "runtime-benchmarks", test))] +pub fn eth_address(key: &k256::ecdsa::SigningKey) -> H160 { + let public_key = key.verifying_key(); + let encoded = public_key.to_encoded_point(false); + // Skip the 0x04 prefix byte to get the uncompressed public key + H160::from_slice(&sp_io::hashing::keccak_256(&encoded.as_bytes()[1..])[12..]) +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index c87805b17b577..b019e2408e22f 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -360,12 +360,9 @@ pub trait EthExtra { match &tx { crate::evm::api::TransactionSigned::Transaction1559Signed(_) | crate::evm::api::TransactionSigned::Transaction2930Signed(_) | - crate::evm::api::TransactionSigned::TransactionLegacySigned(_) => { - // Supported transaction types, continue processing - }, + crate::evm::api::TransactionSigned::TransactionLegacySigned(_) | crate::evm::api::TransactionSigned::Transaction7702Signed(_) => { - log::debug!(target: LOG_TARGET, "EIP-7702 transactions are not supported"); - return Err(InvalidTransaction::Call); + // Supported transaction types, continue processing }, crate::evm::api::TransactionSigned::Transaction4844Signed(_) => { log::debug!(target: LOG_TARGET, "EIP-4844 transactions are not supported"); @@ -445,7 +442,8 @@ mod test { evm::*, test_utils::*, tests::{ - Address, ExtBuilder, RuntimeCall, RuntimeOrigin, SignedExtra, Test, UncheckedExtrinsic, + Address, ExtBuilder, RuntimeCall, RuntimeOrigin, SignedExtra, Test, TestSigner, + UncheckedExtrinsic, }, }; use frame_support::traits::fungible::Mutate; @@ -507,7 +505,9 @@ mod test { let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone(), Default::default()); - self.tx.gas_price = Some(>::evm_base_fee()); + let base_fee = >::evm_base_fee(); + self.tx.gas_price = Some(base_fee); + self.tx.max_fee_per_gas = Some(base_fee); match dry_run { Ok(dry_run) => { @@ -527,6 +527,18 @@ mod test { builder } + /// Create a new builder with a call that includes an EIP-7702 authorization list. + fn call_with_authorization( + dest: H160, + authorization_list: Vec, + ) -> Self { + let mut builder = Self::new(); + builder.tx.to = Some(dest); + builder.tx.r#type = Some(TYPE_EIP7702.into()); + builder.tx.authorization_list = authorization_list; + builder + } + /// Create a new builder with an instantiate call. fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); @@ -857,4 +869,80 @@ mod test { "Chain Id in the generic transaction is not None" ); } + + #[test] + fn check_eth_transact_7702_call_works() { + let chain_id = U256::from(::ChainId::get()); + let signer = TestSigner::new(&[0xCC; 32]); + let auth = signer.sign_authorization(chain_id, H160::from([1u8; 20]), U256::zero()); + + let builder = + UncheckedExtrinsicBuilder::call_with_authorization(H160::from([1u8; 20]), vec![auth]); + let (expected_encoded_len, call, _, tx, weight_required, _) = builder.check().unwrap(); + + match call { + RuntimeCall::Contracts(crate::Call::eth_call:: { + dest, + weight_limit, + encoded_len, + authorization_list, + .. + }) if dest == tx.to.unwrap() => { + assert_eq!(encoded_len, expected_encoded_len); + assert_eq!(authorization_list.len(), 1); + assert!( + weight_limit.all_gte(weight_required), + "weight_limit={weight_limit:?} >= weight_required={weight_required:?}" + ); + }, + _ => panic!("Call does not match."), + } + } + + #[test] + fn check_eth_transact_7702_insufficient_gas() { + use crate::evm::fees::InfoT; + + let chain_id = U256::from(::ChainId::get()); + let dest = H160::from([1u8; 20]); + let auths: Vec<_> = (0..3u8) + .map(|i| { + let mut seed = [0u8; 32]; + seed[0] = 0xCC + i; + TestSigner::new(&seed).sign_authorization(chain_id, dest, U256::zero()) + }) + .collect(); + let num_auths = auths.len() as u128; + let gas_scale = ::GasScale::get() as u128; + let auth_cost = Pallet::::worst_case_delegation_deposit().saturating_mul(num_auths); + + // With estimated gas the transaction is valid + UncheckedExtrinsicBuilder::call_with_authorization(dest, auths.clone()) + .check() + .expect("estimated gas should pass validation"); + + // Gas covering auth deposits + base transaction overhead is sufficient. + UncheckedExtrinsicBuilder::call_with_authorization(dest, auths.clone()) + .mutate_estimate_and_check(Box::new(move |tx| { + let mut call_info = tx + .clone() + .into_call::(CreateCallMode::DryRun) + .expect("dry run should succeed"); + let base_info = ::FeeInfo::base_dispatch_info(&mut call_info.call); + let overhead = ::FeeInfo::fixed_fee(call_info.encoded_len as u32) + + ::FeeInfo::weight_to_fee(&base_info.total_weight()); + let sufficient_gas = 1 + (auth_cost + overhead) / gas_scale; + tx.gas = Some(U256::from(sufficient_gas)); + })) + .expect("gas covering auth deposits + overhead should pass"); + + // Gas covering only auth deposits (without overhead) is insufficient: + let res = UncheckedExtrinsicBuilder::call_with_authorization(dest, auths) + .mutate_estimate_and_check(Box::new(move |tx| { + let insufficient_gas = auth_cost / gas_scale; + tx.gas = Some(U256::from(insufficient_gas)); + })); + + assert_eq!(res, Err(TransactionValidityError::Invalid(InvalidTransaction::Payment))); + } } diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 66c4b1a307671..37196b778a56b 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -74,7 +74,8 @@ impl Tracing for CallTracer { &mut self, from: H160, to: H160, - delegate_call: Option, + _code_address: Option, + is_delegate_call: bool, is_read_only: bool, value: U256, input: &[u8], @@ -104,7 +105,7 @@ impl Tracing for CallTracer { None => { let call_type = if is_read_only { CallType::StaticCall - } else if delegate_call.is_some() { + } else if is_delegate_call { CallType::DelegateCall } else { CallType::Call diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 5a74d1077aa8b..de2681a6be05e 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -240,7 +240,8 @@ impl Tracing for ExecutionTracer { &mut self, _from: H160, _to: H160, - _delegate_call: Option, + _code_address: Option, + _is_delegate_call: bool, _is_read_only: bool, _value: U256, _input: &[u8], diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index 824c92bea932d..d4d58f4e428c7 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -254,20 +254,24 @@ where &mut self, from: H160, to: H160, - delegate_call: Option, + code_address: Option, + is_delegate_call: bool, _is_read_only: bool, _value: U256, _input: &[u8], _gas_limit: u64, ) { - if let Some(delegate_call) = delegate_call { + if is_delegate_call { self.calls.push(self.current_addr()); - self.read_account(delegate_call); } else { self.calls.push(to); - self.read_account(from); } + if let Some(code_address) = code_address { + self.read_account(code_address); + } + self.read_account(from); + if self.create_code.take().is_some() { self.created_addrs.insert(to); } else { diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 2ade8452cd63b..768696e40ee63 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -665,6 +665,12 @@ struct Frame { /// The delegate call info of the currently executing frame which was spawned by /// `delegate_call`. delegate: Option>, + /// The address where the code (and immutable data) originates from. + /// + /// For regular contracts, this equals the contract's own address. + /// For delegated accounts (EIP-7702), this is the delegation target's address. + /// For explicit delegate_call, this is the callee's address. + code_address: H160, /// The output of the last executed call frame. last_frame_output: ExecReturnValue, /// The set of contracts that were created during this call stack. @@ -867,6 +873,7 @@ where T::AddressMapper::to_address(&dest), None, false, + false, value, &input_data, Default::default(), @@ -1157,12 +1164,29 @@ where }, }; + // Compute the code_address: the address where the code (and immutable data) comes from. + // For delegate_call this is the callee, for EIP-7702 delegated accounts it's the + // delegation target, otherwise it's the account's own address. + let address = T::AddressMapper::to_address(&account_id); + let code_address = delegate + .as_ref() + .map(|d| d.callee) + .or_else(|| { + // Constructors can't be delegated, skip the storage read. + if entry_point == ExportedFunction::Constructor { + return None; + } + AccountInfo::::get_delegation_target(&address) + }) + .unwrap_or(address); + let frame = Frame { delegate, value_transferred, contract_info, account_id, entry_point, + code_address, frame_meter: meter.new_nested(call_resources)?, allows_reentry: true, read_only, @@ -1258,7 +1282,8 @@ where tracer.enter_child_span( from, to, - frame.delegate.as_ref().map(|delegate| delegate.callee), + (frame.code_address != to).then_some(frame.code_address), + frame.delegate.is_some(), frame.read_only, frame.value_transferred, &input_data, @@ -1996,13 +2021,9 @@ where return Err(Error::::InvalidImmutableAccess.into()); } - // Immutable is read from contract code being executed - let address = self - .top_frame() - .delegate - .as_ref() - .map(|d| d.callee) - .unwrap_or(T::AddressMapper::to_address(self.account_id())); + // Immutable data is read from the address where the code originates. + // This handles regular contracts, delegated accounts (EIP-7702), and delegate_call. + let address = self.top_frame().code_address; Ok(>::get(address).ok_or_else(|| Error::::InvalidImmutableAccess)?) } @@ -2157,6 +2178,7 @@ where T::AddressMapper::to_address(self.account_id()), T::AddressMapper::to_address(&dest), None, + false, is_read_only, value, &input_data, @@ -2306,6 +2328,12 @@ where return sp_io::hashing::keccak_256(code).into(); } + // EIP-7702: delegated EOAs return keccak256(0xef0100 || target) + if let Some(target) = >::get_delegation_target(address) { + let indicator = >::delegation_indicator(&target); + return sp_io::hashing::keccak_256(&indicator).into(); + } + >::load_contract(&address) .map(|contract| contract.code_hash) .unwrap_or_else(|| { @@ -2326,6 +2354,11 @@ where return code.len() as u64; } + // EIP-7702: delegated EOAs return the delegation indicator size (0xef0100 || target) + if >::is_delegated(address) { + return 23; + } + >::load_contract(&address) .and_then(|contract| CodeInfoOf::::get(contract.code_hash)) .map(|info| info.code_len()) @@ -2468,15 +2501,27 @@ where return; } - let code_hash = self.code_hash(address); - let code = crate::PristineCode::::get(&code_hash).unwrap_or_default(); + let code = if let Some(code) = + >::code(address.as_fixed_bytes()).or_else(|| { + self.exec_config + .mock_handler + .as_ref() + .and_then(|handler| handler.mocked_code(*address)) + }) { + code.to_vec() + } else if let Some(target) = >::get_delegation_target(address) { + // EIP-7702: delegated EOAs return 0xef0100 || target as their code + >::delegation_indicator(&target).to_vec() + } else { + let code_hash = self.code_hash(address); + crate::PristineCode::::get(&code_hash).unwrap_or_default() + }; - let len = len.min(code.len().saturating_sub(code_offset)); - if len > 0 { - buf[..len].copy_from_slice(&code[code_offset..code_offset + len]); + let copy_len = len.min(code.len().saturating_sub(code_offset)); + if copy_len > 0 { + buf[..copy_len].copy_from_slice(&code[code_offset..code_offset + copy_len]); } - - buf[len..].fill(0); + buf[copy_len..].fill(0); } fn terminate_caller(&mut self, beneficiary: &H160) -> Result<(), DispatchError> { @@ -2485,10 +2530,14 @@ where ensure!(parent.entry_point == ExportedFunction::Call, Error::::TerminatedInConstructor); ensure!(parent.delegate.is_none(), Error::::PrecompileDelegateDenied); + let contract_address = T::AddressMapper::to_address(&parent.account_id); + + // EIP-7702: delegated EOAs cannot be destroyed via the system precompile + ensure!(!AccountInfo::::is_delegated(&contract_address), Error::::ContractNotFound); + let info = parent.contract_info(); let trie_id = info.trie_id.clone(); let code_hash = info.code_hash; - let contract_address = T::AddressMapper::to_address(&parent.account_id); let beneficiary = T::AddressMapper::to_account_id(beneficiary); let parent_account_id = parent.account_id.clone(); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 966978bfab5c8..a544531ad93fb 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -53,8 +53,8 @@ pub mod weights; use crate::{ evm::{ CallTracer, CreateCallMode, ExecutionTracer, GenericTransaction, PrestateTracer, - TYPE_EIP1559, Trace, Tracer, TracerType, block_hash::EthereumBlockBuilderIR, block_storage, - fees::InfoT as FeeInfo, runtime::SetWeightLimit, + TYPE_EIP1559, TYPE_EIP7702, Trace, Tracer, TracerType, block_hash::EthereumBlockBuilderIR, + block_storage, fees::InfoT as FeeInfo, runtime::SetWeightLimit, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, sp_runtime::TransactionOutcome, @@ -64,7 +64,7 @@ use crate::{ weightinfo_extension::OnFinalizeBlockParts, }; use alloc::{boxed::Box, format, vec}; -use codec::{Codec, Decode, Encode}; +use codec::{Codec, Decode, Encode, MaxEncodedLen}; use environmental::*; use frame_support::{ BoundedVec, @@ -1321,8 +1321,8 @@ pub mod pallet { /// * `transaction_encoded`: The RLP encoding of the signed Ethereum transaction, /// represented as [crate::evm::TransactionSigned], provided by the Ethereum wallet. This /// is used for building the Ethereum transaction root. - /// * effective_gas_price: the price of a unit of gas - /// * encoded len: the byte code size of the `eth_transact` extrinsic + /// * `effective_gas_price`: the price of a unit of gas + /// * `encoded_len`: the byte code size of the `eth_transact` extrinsic /// /// Calling this dispatchable ensures that the origin's nonce is bumped only once, /// via the `CheckNonce` transaction extension. In contrast, [`Self::instantiate_with_code`] @@ -1372,6 +1372,7 @@ pub mod pallet { eth_gas_limit: eth_gas_limit.saturated_into(), weight_limit, eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + authorization_deposit: Default::default(), }, Code::Upload(code), data, @@ -1404,13 +1405,15 @@ pub mod pallet { /// * `transaction_encoded`: The RLP encoding of the signed Ethereum transaction, /// represented as [crate::evm::TransactionSigned], provided by the Ethereum wallet. This /// is used for building the Ethereum transaction root. - /// * effective_gas_price: the price of a unit of gas - /// * encoded len: the byte code size of the `eth_transact` extrinsic + /// * `effective_gas_price`: the price of a unit of gas + /// * `encoded_len`: the byte code size of the `eth_transact` extrinsic + /// * `authorization_list`: EIP-7702 authorization tuples to process before execution #[pallet::call_index(11)] #[pallet::weight( T::WeightInfo::eth_call(Pallet::::has_dust(*value).into()) .saturating_add(*weight_limit) .saturating_add(T::WeightInfo::on_finalize_block_per_tx(transaction_encoded.len() as u32)) + .saturating_add(T::WeightInfo::process_new_account_authorization(authorization_list.len() as u32)) )] pub fn eth_call( origin: OriginFor, @@ -1422,6 +1425,7 @@ pub mod pallet { transaction_encoded: Vec, effective_gas_price: U256, encoded_len: u32, + authorization_list: Vec, ) -> DispatchResultWithPostInfo { let signer = Self::ensure_eth_signed(origin)?; let origin = OriginFor::::signed(signer.clone()); @@ -1436,14 +1440,27 @@ pub mod pallet { transaction_encoded: transaction_encoded.clone(), effective_gas_price, encoded_len, + authorization_list: authorization_list.clone(), } .into(); let info = T::FeeInfo::dispatch_info(&call); let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); + let exec_config = + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, base_info.total_weight()); + let auth_result = evm::eip7702::process_authorizations::( + &authorization_list, + &signer, + &exec_config, + ) + .inspect_err(|e| { + log::error!(target: LOG_TARGET, "process_authorizations failed: {e:?}. This is a bug: the transaction should have failed validation."); + })?; + let extra_weight = base_info.total_weight().saturating_sub(auth_result.weight_refund); + let base_call_weight = base_info.call_weight.saturating_sub(auth_result.weight_refund); + block_storage::with_ethereum_context::(transaction_encoded, || { - let extra_weight = base_info.total_weight(); let output = Self::bare_call( origin, dest, @@ -1452,6 +1469,7 @@ pub mod pallet { eth_gas_limit: eth_gas_limit.saturated_into(), weight_limit, eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + authorization_deposit: auth_result.deposit, }, data, &ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), @@ -1460,7 +1478,7 @@ pub mod pallet { block_storage::EthereumCallResult::new::( signer, output, - base_info.call_weight, + base_call_weight, encoded_len, &info, effective_gas_price, @@ -2171,10 +2189,13 @@ impl Pallet { tx.gas = Some(Self::evm_block_gas_limit()); } if tx.r#type.is_none() { - tx.r#type = Some(TYPE_EIP1559.into()); + tx.r#type = Some( + if tx.authorization_list.is_empty() { TYPE_EIP1559 } else { TYPE_EIP7702 }.into(), + ); } // Store values before moving the tx + let authorization_list = tx.authorization_list.clone(); let value = tx.value.unwrap_or_default(); let input = tx.input.clone().to_vec(); let from = tx.from; @@ -2190,11 +2211,8 @@ impl Pallet { // in those cases we skip the check that the caller has enough balance // to pay for the fees let base_info = T::FeeInfo::base_dispatch_info(&mut call_info.call); - let base_weight = base_info.total_weight(); + let mut base_weight = base_info.total_weight(); let perform_balance_checks = dry_run_config.perform_balance_checks; - let exec_config = - ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight) - .with_dry_run(dry_run_config); // emulate transaction behavior let fees = call_info.tx_fee.saturating_add(call_info.storage_deposit); @@ -2224,10 +2242,28 @@ impl Pallet { } }; + let exec_config = + ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight); + let auth_result = if !authorization_list.is_empty() { + evm::eip7702::process_authorizations::(&authorization_list, &origin, &exec_config) + .map_err(|err| extract_error(err).unwrap_err())? + } else { + Default::default() + }; + base_weight = base_weight.saturating_sub(auth_result.weight_refund); + let actual_auth_deposit = auth_result.deposit; + let worst_case_auth_deposit = Self::worst_case_delegation_deposit() + .saturating_mul(authorization_list.len().saturated_into()); + + let exec_config = + ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight) + .with_dry_run(dry_run_config); + let transaction_limits = TransactionLimits::EthereumGas { eth_gas_limit: call_info.eth_gas_limit.saturated_into(), weight_limit: Self::evm_max_extrinsic_weight(), eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), + authorization_deposit: actual_auth_deposit, }; // Dry run the call @@ -2327,6 +2363,11 @@ impl Pallet { }, }; + // Ensure max_storage_deposit covers worst-case authorization cost for pool validation. + // The meter already includes the actual auth deposit; this bumps it to worst case + // so that the gas estimate produces a transaction that passes pool validation. + dry_run.max_storage_deposit = dry_run.max_storage_deposit.max(worst_case_auth_deposit); + // replace the weight passed in the transaction with the dry_run result call_info.call.set_weight_limit(dry_run.weight_required); @@ -2679,15 +2720,24 @@ impl Pallet { /// Returns the code at `address`. /// /// This takes pre-compiles into account. + /// For EIP-7702 delegated accounts, returns the delegation indicator (0xef0100 || target). pub fn code(address: &H160) -> Vec { use precompiles::{All, Precompiles}; if let Some(code) = >::code(address.as_fixed_bytes()) { return code.into(); } - AccountInfo::::load_contract(&address) - .and_then(|contract| >::get(contract.code_hash)) - .map(|code| code.into()) - .unwrap_or_default() + + let Some(info) = >::get(address) else { return Vec::new() }; + + match info.account_type { + AccountType::Contract(contract) => >::get(contract.code_hash) + .map(|code| code.into()) + .unwrap_or_default(), + AccountType::DelegatedEOA { delegate_target: Some(target), .. } => { + AccountInfo::::delegation_indicator(&target).to_vec() + }, + AccountType::EOA | AccountType::DelegatedEOA { .. } => Vec::new(), + } } /// Uploads new code and returns the Vm binary contract blob and deposit amount collected. @@ -2749,7 +2799,7 @@ impl Pallet { /// /// `dst` is usually the transaction origin and `from` a contract or /// the pallets own account. - fn refund_deposit( + pub(crate) fn refund_deposit( hold_reason: HoldReason, from: &T::AccountId, dst: deposit_payment::Funds, @@ -2804,6 +2854,19 @@ impl Pallet { >>::minimum_balance() } + /// Worst-case storage deposit for a single EIP-7702 authorization. + /// + /// Assumes a new account delegating to a contract with the maximum code size. + pub(crate) fn worst_case_delegation_deposit() -> BalanceOf { + let ed = ::Currency::minimum_balance(); + let contract_deposit = T::DepositPerByte::get() + .saturating_mul((>::max_encoded_len() as u32).into()) + .saturating_add(T::DepositPerItem::get()); + let max_code_deposit = vm::calculate_code_deposit::(limits::code::BLOB_BYTES); + let code_lockup = T::CodeHashLockupDepositPercent::get().mul_ceil(max_code_deposit); + ed.saturating_add(contract_deposit).saturating_add(code_lockup) + } + /// Deposit a pallet revive event. /// /// This method will be called by the EVM to deposit events emitted by the contract. diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index dda7b72cbfb5f..dd8182baab1bb 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -289,6 +289,7 @@ pub mod ethereum_execution { eth_gas_limit, weight_limit, eth_tx_info, + authorization_deposit: Zero::zero(), }, _phantom: PhantomData, }; diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 47195f32431f2..b7a7ee5318593 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -131,6 +131,9 @@ pub enum TransactionLimits { weight_limit: Weight, /// Some extra information about the transaction that is required to calculate gas usage. eth_tx_info: EthTxInfo, + /// Deposit consumed by EIP-7702 authorization processing before contract execution. + /// Recorded on the meter at creation to correctly reduce the available deposit budget. + authorization_deposit: BalanceOf, }, /// Substrate execution mode: the transaction specifies a weight limit and a storage deposit /// limit @@ -528,13 +531,23 @@ impl TransactionMeter { ); let mut transaction_meter = match transaction_limits { - TransactionLimits::EthereumGas { eth_gas_limit, weight_limit, eth_tx_info } => { - math::ethereum_execution::new_root(eth_gas_limit, weight_limit, eth_tx_info) + TransactionLimits::EthereumGas { + eth_gas_limit, + weight_limit, + eth_tx_info, + authorization_deposit, + } => { + let mut meter = + math::ethereum_execution::new_root(eth_gas_limit, weight_limit, eth_tx_info)?; + if !authorization_deposit.is_zero() { + meter.deposit.record_charge(&StorageDeposit::Charge(authorization_deposit)); + } + meter }, TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => { - math::substrate_execution::new_root(weight_limit, deposit_limit) + math::substrate_execution::new_root(weight_limit, deposit_limit)? }, - }?; + }; transaction_meter.adjust_effective_weight_limit()?; @@ -713,6 +726,17 @@ impl EthTxInfo { deposit_gas.saturating_add(&weight_gas) } + /// Compute the maximum deposit available from a gas budget assuming zero execution weight + /// and zero deposit consumed. This is the upper bound on how much deposit the transaction + /// could ever spend. + pub fn max_deposit(&self, eth_gas_limit: BalanceOf) -> BalanceOf { + let max_gas = SignedGas::::from_ethereum_gas(eth_gas_limit); + let overhead_gas = + self.gas_consumption(&Weight::zero(), &DepositOf::::Charge(Zero::zero())); + let remaining = max_gas.saturating_sub(&overhead_gas); + remaining.to_adjusted_deposit_charge().unwrap_or_default() + } + /// Calculate maximal possible remaining weight that can be consumed given a particular gas /// limit. /// diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index c6f56f321c8aa..6760b68267293 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -262,6 +262,7 @@ fn substrate_metering_initialization_works() { eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), weight_limit: Weight::MAX, eth_tx_info, + authorization_deposit: 0, }); if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining { @@ -300,6 +301,7 @@ fn substrate_metering_initialization_works() { eth_gas_limit: 5_000_000_000 / gas_scale, weight_limit: Weight::from_parts(ref_time_limit, proof_size_limit), eth_tx_info, + authorization_deposit: 0, }) .unwrap(); @@ -392,6 +394,7 @@ fn substrate_metering_charges_works() { eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), weight_limit: Weight::MAX, eth_tx_info, + authorization_deposit: 0, }) .unwrap(); @@ -619,6 +622,7 @@ fn substrate_nesting_works() { eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), weight_limit: Weight::MAX, eth_tx_info: eth_tx_info.clone(), + authorization_deposit: 0, }) .unwrap(); @@ -723,6 +727,7 @@ fn substrate_nesting_charges_works() { eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), weight_limit: Weight::MAX, eth_tx_info, + authorization_deposit: 0, }) .unwrap(); @@ -839,6 +844,7 @@ fn catch_constructor_test() { eth_gas_limit: eth_gas_limit.into(), weight_limit: Weight::MAX, eth_tx_info: crate::EthTxInfo::new(0, Default::default()), + authorization_deposit: 0, }) .build() }; diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index 19c69de94b029..787faf31499ca 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -18,12 +18,14 @@ //! This module contains routines for accessing and altering a contract related state. use crate::{ - AccountInfoOf, BalanceOf, BalanceWithDust, Config, DeletionQueue, DeletionQueueCounter, Error, - NativeDepositOf, SENTINEL, TrieId, + AccountInfoOf, BalanceOf, BalanceWithDust, CodeInfoOf, Config, DeletionQueue, + DeletionQueueCounter, Error, LOG_TARGET, NativeDepositOf, SENTINEL, TrieId, address::AddressMapper, exec::{AccountIdOf, Key}, metering::FrameMeter, + primitives::StorageDeposit, tracing::if_tracing, + vm::CodeInfo, weights::WeightInfo, }; use alloc::vec::Vec; @@ -78,9 +80,18 @@ pub enum AccountType { /// An account that is a contract. Contract(ContractInfo), - /// An account that is an externally owned account (EOA). + /// An externally owned account (no delegation). #[default] EOA, + + /// An EOA that has been delegated via EIP-7702. + /// Once delegated, the account stays `DelegatedEOA` even after clearing. + DelegatedEOA { + /// When `Some`, the account delegates code execution to that address. + delegate_target: Option, + /// Storage accounting for this EOA's child trie. + contract_info: ContractInfo, + }, } /// Information for managing an account and its sub trie abstraction. @@ -139,6 +150,24 @@ impl From> for AccountType { } } +impl AccountType { + /// Returns the ContractInfo if this account type has loadable contract code. + /// + /// For `DelegatedEOA`, only returns `Some` when delegation is active and the + /// code_hash is non-default (i.e., the target is a contract). + pub fn contract_info(self) -> Option> { + match self { + AccountType::Contract(info) => Some(info), + AccountType::DelegatedEOA { delegate_target: Some(_), contract_info } + if !contract_info.code_hash.is_zero() => + { + Some(contract_info) + }, + _ => None, + } + } +} + impl AccountInfo { /// Returns true if the account is a contract. pub fn is_contract(address: &H160) -> bool { @@ -169,23 +198,208 @@ impl AccountInfo { BalanceWithDust::new_unchecked::(value, dust) } - /// Loads the contract information for a given address. + /// Loads the ContractInfo for a given address. pub fn load_contract(address: &H160) -> Option> { - let Some(info) = >::get(address) else { return None }; - let AccountType::Contract(contract_info) = info.account_type else { return None }; - Some(contract_info) + >::get(address)?.account_type.contract_info() } /// Insert a contract, existing dust if any will be unchanged. pub fn insert_contract(address: &H160, contract: ContractInfo) { AccountInfoOf::::mutate(address, |account| { if let Some(account) = account { - account.account_type = contract.clone().into(); + match &mut account.account_type { + AccountType::DelegatedEOA { contract_info, .. } => { + *contract_info = contract; + return; + }, + _ => account.account_type = contract.into(), + } } else { - *account = Some(AccountInfo { account_type: contract.clone().into(), dust: 0 }); + *account = Some(AccountInfo { account_type: contract.into(), dust: 0 }); } }); } + + /// Updates the ContractInfo for storage operations at a given address. + pub fn update_contract_info(address: &H160, contract_info: ContractInfo) { + AccountInfoOf::::mutate(address, |account| { + if let Some(account) = account { + match &mut account.account_type { + AccountType::Contract(info) => *info = contract_info, + AccountType::DelegatedEOA { contract_info: info, .. } => *info = contract_info, + AccountType::EOA => {}, + } + } + }); + } + + /// EIP-7702: Check if an account has a delegation indicator set + pub fn is_delegated(address: &H160) -> bool { + let Some(info) = >::get(address) else { return false }; + matches!(info.account_type, AccountType::DelegatedEOA { delegate_target: Some(_), .. }) + } + + /// EIP-7702: Get the delegation target for an address + pub fn get_delegation_target(address: &H160) -> Option { + let info = >::get(address)?; + match info.account_type { + AccountType::DelegatedEOA { delegate_target: Some(target), .. } => Some(target), + _ => None, + } + } + + /// EIP-7702: Build the 23-byte delegation indicator `0xef0100 || target`. + pub fn delegation_indicator(target: &H160) -> [u8; 23] { + let mut buf = [0u8; 23]; + buf[0] = 0xef; + buf[1] = 0x01; + buf[2] = 0x00; + buf[3..23].copy_from_slice(target.as_bytes()); + buf + } + + /// EIP-7702: Set a delegation indicator for an EOA + /// + /// Marks the account as delegated to the target address. + /// Per EIP-7702, only creates ContractInfo if target is a Contract. + /// If target is delegated or EOA, contract_info is None (no chain following). + /// Existing contract_info (deposit accounting) is preserved across re-delegations. + /// + /// Returns the net deposit change. + /// + /// Note: the target's `code_hash` is snapshotted at delegation time. This is fine + /// because `set_code` (the only way to change a contract's code) requires root. + pub(crate) fn set_delegation( + address: &H160, + target: H160, + ) -> Result>, DispatchError> { + let target_code_hash = + >::get(&target).and_then(|info| match info.account_type { + AccountType::Contract(c) => Some(c.code_hash), + _ => None, + }); + + let code_deposit = target_code_hash + .and_then(|ch| CodeInfoOf::::get(ch)) + .map(|info| info.deposit()); + + let mut new_deposit: BalanceOf = Zero::zero(); + let mut old_deposit: BalanceOf = Zero::zero(); + + // Update an existing ContractInfo, adjusting code_hash and deposit. + let update_contract_info = |contract_info: &mut ContractInfo, + new_deposit: &mut BalanceOf| { + if let Some(code_hash) = target_code_hash { + contract_info.code_hash = code_hash; + if let Some(cd) = code_deposit { + *new_deposit = contract_info.update_base_deposit(cd); + } + } else { + // Target is not a contract: clear stale code_hash and deposit so that + // a subsequent re-delegation doesn't double-decrement the refcount + // or double-refund the deposit. + contract_info.code_hash = Default::default(); + contract_info.storage_base_deposit = Default::default(); + } + }; + + let old_code_hash = AccountInfoOf::::mutate(address, |account| { + let mut old_code_hash = None; + + if let Some(account) = account { + match &mut account.account_type { + AccountType::DelegatedEOA { delegate_target, contract_info } => { + // Filter out zeroed hashes: after delegating to a non-contract, + // code_hash is cleared to default but contract_info is preserved. + old_code_hash = Some(contract_info.code_hash).filter(|h| !h.is_zero()); + old_deposit = contract_info.storage_base_deposit; + *delegate_target = Some(target); + update_contract_info(contract_info, &mut new_deposit); + }, + ty => { + debug_assert!( + !matches!(ty, AccountType::Contract(_)), + "set_delegation must not be called on contract accounts" + ); + let mut contract_info = ContractInfo::::new_for_delegation( + address, + target_code_hash.unwrap_or_default(), + ); + update_contract_info(&mut contract_info, &mut new_deposit); + account.account_type = AccountType::DelegatedEOA { + delegate_target: Some(target), + contract_info, + }; + }, + } + } else { + let mut contract_info = ContractInfo::::new_for_delegation( + address, + target_code_hash.unwrap_or_default(), + ); + update_contract_info(&mut contract_info, &mut new_deposit); + *account = Some(AccountInfo { + account_type: AccountType::DelegatedEOA { + delegate_target: Some(target), + contract_info, + }, + dust: 0, + }); + } + + old_code_hash + }); + + // Manage code refcounts, skipping when the hash is unchanged + if let Some(new_hash) = target_code_hash && + Some(new_hash) != old_code_hash + { + CodeInfo::::increment_refcount(new_hash).inspect_err(|e| { + log::warn!(target: LOG_TARGET, "increment_refcount({new_hash:?}) failed: {e:?}"); + })?; + } + if let Some(old_hash) = old_code_hash && + Some(old_hash) != target_code_hash + { + let _ = CodeInfo::::decrement_refcount(old_hash).inspect_err(|e| { + log::warn!(target: LOG_TARGET, "decrement_refcount({old_hash:?}) failed: {e:?}"); + })?; + } + + Ok(if new_deposit >= old_deposit { + StorageDeposit::Charge(new_deposit.saturating_sub(old_deposit)) + } else { + StorageDeposit::Refund(old_deposit.saturating_sub(new_deposit)) + }) + } + + /// EIP-7702: Clear delegation indicator. + /// + /// The account stays `DelegatedEOA` with `delegate_target = None` so that + /// the child trie and deposit accounting are preserved for future re-delegation. + /// + /// Returns the `storage_base_deposit` to refund. + pub(crate) fn clear_delegation( + address: &H160, + ) -> Result>, DispatchError> { + AccountInfoOf::::mutate(address, |account| { + let mut refund: BalanceOf = Zero::zero(); + if let Some(account) = account && + let AccountType::DelegatedEOA { delegate_target, contract_info } = + &mut account.account_type + { + *delegate_target = None; + if !contract_info.code_hash.is_zero() { + let _ = CodeInfo::::decrement_refcount(contract_info.code_hash).inspect_err(|e| { + log::warn!(target: LOG_TARGET, "decrement_refcount({:?}) failed: {e:?}", contract_info.code_hash); + })?; + refund = core::mem::take(&mut contract_info.storage_base_deposit); + contract_info.code_hash = Default::default(); + } + } + Ok(StorageDeposit::Refund(refund)) + }) + } } impl ContractInfo { @@ -233,6 +447,32 @@ impl ContractInfo { Ok(contract) } + /// Constructs a new contract info for a delegated account (EIP-7702). + /// + /// Delegated accounts have their own child trie for storage but use the code hash + /// of the target contract they delegate to. The trie_id is derived solely from the + /// address so that storage persists across re-delegations to different targets. + pub fn new_for_delegation(address: &H160, target_code_hash: sp_core::H256) -> Self { + let trie_id = { + let buf = ("delegated_trie_v1", address).using_encoded(T::Hashing::hash); + buf.as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") + }; + + Self { + trie_id, + code_hash: target_code_hash, + storage_bytes: 0, + storage_items: 0, + storage_byte_deposit: Zero::zero(), + storage_item_deposit: Zero::zero(), + storage_base_deposit: Zero::zero(), + immutable_data_len: 0, + } + } + /// Associated child trie unique id is built from the hash part of the trie id. pub fn child_trie_info(&self) -> ChildInfo { ChildInfo::new_default(self.trie_id.as_ref()) diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index d370a3d199a87..bb659038d1491 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -305,6 +305,7 @@ builder!( transaction_encoded: Vec, effective_gas_price: U256, encoded_len: u32, + authorization_list: Vec, ) -> DispatchResultWithPostInfo; /// Create a [`EthCallBuilder`] with default values. @@ -319,6 +320,7 @@ builder!( transaction_encoded: TransactionSigned::TransactionLegacySigned(Default::default()).signed_payload(), effective_gas_price: 0u32.into(), encoded_len: 0, + authorization_list: vec![], } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index a7cbe49f93929..7b982d3123453 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -17,6 +17,7 @@ mod block_hash; mod deposit_payment; +mod eip7702; mod pallet_dummy; mod precompiles; mod pvm; @@ -86,6 +87,36 @@ impl EthExtra for EthExtraImpl { } } +/// Helper function to generate a simple dummy EVM contract +/// Returns bytecode that stores a value (42) in memory and returns it +pub(crate) fn dummy_evm_contract() -> Vec { + use revm::bytecode::opcode::*; + vec![PUSH1, 0x2a, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN] +} + +/// Test keypair for signing EIP-7702 authorizations +pub(crate) struct TestSigner { + key: k256::ecdsa::SigningKey, + pub address: H160, +} + +impl TestSigner { + pub fn new(seed: &[u8; 32]) -> Self { + let key = k256::ecdsa::SigningKey::from_bytes(seed.into()).expect("valid key; qed"); + let address = crate::evm::eip7702::eth_address(&key); + Self { key, address } + } + + pub fn sign_authorization( + &self, + chain_id: U256, + address: H160, + nonce: U256, + ) -> crate::evm::AuthorizationListEntry { + crate::evm::eip7702::sign_authorization(&self.key, chain_id, address, nonce) + } +} + frame_support::construct_runtime!( pub enum Test { diff --git a/substrate/frame/revive/src/tests/eip7702.rs b/substrate/frame/revive/src/tests/eip7702.rs new file mode 100644 index 0000000000000..1231f574534fc --- /dev/null +++ b/substrate/frame/revive/src/tests/eip7702.rs @@ -0,0 +1,1177 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for EIP-7702: Set EOA Account Code + +use crate::{ + Code, CodeInfoOf, Config, ExecConfig, HoldReason, + evm::{AuthorizationListEntry, eip7702::AuthorizationResult, fees::InfoT}, + storage::AccountInfo, + test_utils::builder::Contract, + tests::{TestSigner, builder, test_utils::*, *}, +}; +use frame_support::{ + assert_ok, + traits::fungible::{Balanced, Inspect, Mutate}, + weights::Weight, +}; +use sp_core::{H160, H256, U256}; + +/// Compute the expected weight refund for a given mix of new/existing processed accounts. +/// Mirrors the logic in `process_authorizations`. +fn expected_weight_refund_for(total: u32, new_accounts: u32, existing_accounts: u32) -> Weight { + use crate::weights::WeightInfo; + let worst = ::WeightInfo::process_new_account_authorization(total) + .saturating_add(::WeightInfo::process_existing_account_authorization(0)); + let actual = ::WeightInfo::process_new_account_authorization(new_accounts) + .saturating_add(::WeightInfo::process_existing_account_authorization( + existing_accounts, + )); + worst.saturating_sub(actual) +} + +fn expected_weight_refund(new_accounts: u32, existing_accounts: u32) -> Weight { + expected_weight_refund_for(new_accounts + existing_accounts, new_accounts, existing_accounts) +} + +/// Common setup for delegation tests that call `process_authorizations` directly. +pub struct DelegationTestSetup { + pub signer: TestSigner, + pub authority_id: AccountId32, + origin: AccountId32, + exec_config: ExecConfig, + chain_id: U256, +} + +impl Default for DelegationTestSetup { + fn default() -> Self { + Self::new([1u8; 32]) + } +} + +impl DelegationTestSetup { + pub fn new(seed: [u8; 32]) -> Self { + let setup = Self::new_unfunded(seed); + let _ = <::Currency as Mutate<_>>::set_balance( + &setup.authority_id, + 100_000_000, + ); + setup + } + + fn new_unfunded(seed: [u8; 32]) -> Self { + let chain_id = U256::from(::ChainId::get()); + let signer = TestSigner::new(&seed); + let authority_id = ::AddressMapper::to_account_id(&signer.address); + ::FeeInfo::deposit_txfee(::Currency::issue(10_000_000_000)); + let origin = ::AddressMapper::to_account_id(&H160::from([0xFF; 20])); + let _ = <::Currency as Mutate<_>>::set_balance(&origin, 10_000_000_000); + let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX); + Self { signer, authority_id, origin, exec_config, chain_id } + } + + pub fn process( + &self, + auths: &[AuthorizationListEntry], + ) -> AuthorizationResult> { + crate::evm::eip7702::process_authorizations::(auths, &self.origin, &self.exec_config) + .expect("process_authorizations failed") + } + + pub fn nonce(&self) -> U256 { + U256::from(frame_system::Pallet::::account_nonce(&self.authority_id)) + } + + /// Sign an authorization for the given target using the current nonce and chain_id. + pub fn sign_authorization(&self, target: H160) -> AuthorizationListEntry { + self.signer.sign_authorization(self.chain_id, target, self.nonce()) + } + + /// Sign, process, and assert delegation succeeded. + pub fn authorize(&self, target: H160) -> AuthorizationResult> { + let auth = self.sign_authorization(target); + let result = self.process(&[auth]); + assert!(AccountInfo::::is_delegated(&self.signer.address)); + result + } +} + +#[test] +fn delegation_storage_basics() { + ExtBuilder::default().build().execute_with(|| { + let authority = H160::from([0x11; 20]); + let target1 = H160::from([0x22; 20]); + let target2 = H160::from([0x33; 20]); + + // Set delegation + AccountInfo::::set_delegation(&authority, target1).unwrap(); + assert!(AccountInfo::::is_delegated(&authority)); + assert_eq!(AccountInfo::::get_delegation_target(&authority), Some(target1)); + + // Update to different target + AccountInfo::::set_delegation(&authority, target2).unwrap(); + assert!(AccountInfo::::is_delegated(&authority)); + assert_eq!(AccountInfo::::get_delegation_target(&authority), Some(target2)); + + // Clear delegation + AccountInfo::::clear_delegation(&authority).unwrap(); + assert!(!AccountInfo::::is_delegated(&authority)); + assert_eq!(AccountInfo::::get_delegation_target(&authority), None); + }); +} + +#[test] +fn regular_contract_is_not_delegation() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 1_000_000_000); + let bytecode = dummy_evm_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(bytecode)).build_and_unwrap_contract(); + + assert!(AccountInfo::::is_contract(&addr)); + assert!(!AccountInfo::::is_delegated(&addr)); + assert_eq!(AccountInfo::::get_delegation_target(&addr), None); + }); +} + +#[test] +fn eip3607_checks() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 1_000_000_000); + + // Delegated EOAs are allowed to originate transactions + let authority = H160::from([0x11; 20]); + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 1_000_000); + AccountInfo::::set_delegation(&authority, H160::from([0x22; 20])).unwrap(); + assert_ok!(Contracts::ensure_non_contract_if_signed(&RuntimeOrigin::signed(authority_id))); + + // Regular contracts are rejected + let Contract { account_id, .. } = + builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + assert!( + Contracts::ensure_non_contract_if_signed(&RuntimeOrigin::signed(account_id)).is_err() + ); + }); +} + +#[test] +fn authorization_happy_path() { + ExtBuilder::default().build().execute_with(|| { + let target = H160::from([0x42; 20]); + let existing_one = AuthorizationResult { + existing_accounts: 1, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund(0, 1), + }; + + // Valid signature → delegated, nonce incremented + let setup = DelegationTestSetup::new([1u8; 32]); + let nonce_before = frame_system::Pallet::::account_nonce(&setup.authority_id); + let auth = setup.sign_authorization(target); + assert_eq!(setup.process(&[auth]), existing_one); + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + assert_eq!(AccountInfo::::get_delegation_target(&setup.signer.address), Some(target)); + assert_eq!( + frame_system::Pallet::::account_nonce(&setup.authority_id), + nonce_before + 1 + ); + + // chain_id = 0 (wildcard) is accepted + let setup = DelegationTestSetup::new([2u8; 32]); + let auth = setup.signer.sign_authorization(U256::zero(), target, setup.nonce()); + assert_eq!(setup.process(&[auth]), existing_one); + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + assert_eq!(AccountInfo::::get_delegation_target(&setup.signer.address), Some(target)); + }); +} + +#[test] +fn invalid_authorization_is_skipped() { + ExtBuilder::default().build().execute_with(|| { + let target = H160::from([0x42; 20]); + let skipped = AuthorizationResult { + existing_accounts: 0, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund_for(1, 0, 0), + }; + + // Wrong chain_id + let setup = DelegationTestSetup::new([1u8; 32]); + let auth = setup.signer.sign_authorization(U256::from(999), target, setup.nonce()); + assert_eq!(setup.process(&[auth]), skipped); + assert!(!AccountInfo::::is_delegated(&setup.signer.address)); + + // Wrong nonce + let setup = DelegationTestSetup::new([2u8; 32]); + let wrong_nonce = setup.nonce().saturating_add(U256::from(1)); + let auth = setup.signer.sign_authorization(setup.chain_id, target, wrong_nonce); + assert_eq!(setup.process(&[auth]), skipped); + assert!(!AccountInfo::::is_delegated(&setup.signer.address)); + + // Corrupted signature + let setup = DelegationTestSetup::new([3u8; 32]); + let auth = AuthorizationListEntry { + chain_id: setup.chain_id, + address: target, + nonce: setup.nonce(), + y_parity: U256::zero(), + r: U256::from(0xdeadbeef_u64), + s: U256::from(0xcafebabe_u64), + }; + assert_eq!(setup.process(&[auth]), skipped); + assert!(!AccountInfo::::is_delegated(&setup.signer.address)); + }); +} + +/// Authorization for an account that is already a contract is skipped. +/// Per EIP-7702, only EOAs can be delegated — contract accounts are ineligible. +#[test] +fn contract_account_rejects_authorization() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let setup = DelegationTestSetup::new([1u8; 32]); + let target = H160::from([0x42; 20]); + + // Deploy a contract and mark the signer's address as a contract + let contract = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + let contract_info = get_contract(&contract.addr); + + // Overwrite the signer's account info to be a Contract type + use crate::storage::AccountType; + crate::AccountInfoOf::::insert( + setup.signer.address, + crate::storage::AccountInfo { + account_type: AccountType::Contract(contract_info), + dust: 0, + }, + ); + assert!(AccountInfo::::is_contract(&setup.signer.address)); + + let auth = setup.sign_authorization(target); + + // Authorization should be skipped because the authority is a contract + assert_eq!( + setup.process(&[auth]), + AuthorizationResult { + existing_accounts: 0, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund_for(1, 0, 0) + } + ); + + // Account should still be a contract, not delegated + assert!(AccountInfo::::is_contract(&setup.signer.address)); + assert!(!AccountInfo::::is_delegated(&setup.signer.address)); + }); +} + +#[test] +fn multiple_authorizations_from_same_authority_first_wins() { + ExtBuilder::default().build().execute_with(|| { + let setup = DelegationTestSetup::new([1u8; 32]); + let target1 = H160::from([0x11; 20]); + let target2 = H160::from([0x22; 20]); + let target3 = H160::from([0x33; 20]); + + let nonce = setup.nonce(); + + // All have the same nonce, but only the first will succeed + // (subsequent ones will fail due to nonce mismatch after first increments it) + let auth1 = setup.signer.sign_authorization(setup.chain_id, target1, nonce); + let auth2 = setup.signer.sign_authorization(setup.chain_id, target2, nonce); + let auth3 = setup.signer.sign_authorization(setup.chain_id, target3, nonce); + + assert_eq!( + setup.process(&[auth1, auth2, auth3]), + AuthorizationResult { + existing_accounts: 1, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund_for(3, 0, 1) + }, + ); + + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + // First authorization wins since we process blindly + assert_eq!( + AccountInfo::::get_delegation_target(&setup.signer.address), + Some(target1) + ); + }); +} + +#[test] +fn new_account_sets_delegation() { + ExtBuilder::default().build().execute_with(|| { + let setup = DelegationTestSetup::new_unfunded([1u8; 32]); + let target = H160::from([0x42; 20]); + + let auth = setup.sign_authorization(target); + + assert_eq!( + setup.process(&[auth]), + AuthorizationResult { + existing_accounts: 0, + new_accounts: 1, + deposit: 1, + weight_refund: expected_weight_refund(1, 0) + }, + ); + + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + assert_eq!(AccountInfo::::get_delegation_target(&setup.signer.address), Some(target)); + let balance = <::Currency as Inspect<_>>::balance(&setup.authority_id); + assert_eq!(balance, Pallet::::min_balance()); + }); +} + +#[test] +fn clearing_delegation_with_zero_address() { + ExtBuilder::default().build().execute_with(|| { + let setup = DelegationTestSetup::new([1u8; 32]); + let target = H160::from([0x42; 20]); + + let auth1 = setup.sign_authorization(target); + + assert_eq!( + setup.process(&[auth1]), + AuthorizationResult { + existing_accounts: 1, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund(0, 1) + }, + ); + + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + + let auth2 = setup.sign_authorization(H160::zero()); + assert_eq!( + setup.process(&[auth2]), + AuthorizationResult { + existing_accounts: 1, + new_accounts: 0, + deposit: 0, + weight_refund: expected_weight_refund(0, 1) + }, + ); + + assert!(!AccountInfo::::is_delegated(&setup.signer.address)); + assert_eq!(AccountInfo::::get_delegation_target(&setup.signer.address), None); + }); +} + +#[test] +fn process_multiple_authorizations_from_different_signers() { + ExtBuilder::default().build().execute_with(|| { + let setup1 = DelegationTestSetup::new([1u8; 32]); + let setup2 = DelegationTestSetup::new([2u8; 32]); + let setup3 = DelegationTestSetup::new_unfunded([3u8; 32]); + let target = H160::from([0x42; 20]); + + let auth1 = setup1.sign_authorization(target); + let auth2 = setup2.sign_authorization(target); + let auth3 = setup3.sign_authorization(target); + + assert_eq!( + setup1.process(&[auth1, auth2, auth3]), + AuthorizationResult { + existing_accounts: 2, + new_accounts: 1, + deposit: 1, + weight_refund: expected_weight_refund(1, 2) + }, + ); + + assert!(AccountInfo::::is_delegated(&setup1.signer.address)); + assert!(AccountInfo::::is_delegated(&setup2.signer.address)); + assert!(AccountInfo::::is_delegated(&setup3.signer.address)); + }); +} + +/// Runtime test: Set and clear authorization via eth_call. +/// Verifies delegation state, nonce increment, and deposit lifecycle. +#[test] +fn test_runtime_set_and_clear_authorization() { + ExtBuilder::default().build().execute_with(|| { + let chain_id = U256::from(::ChainId::get()); + + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + ::FeeInfo::deposit_txfee(::Currency::issue(10_000_000_000)); + + let seed = H256::from([1u8; 32]); + let signer = TestSigner::new(&seed.0); + let authority = signer.address; + + let target_contract = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 100_000_000); + + // Set delegation + let nonce = U256::from(frame_system::Pallet::::account_nonce(&authority_id)); + let auth1 = signer.sign_authorization(chain_id, target_contract.addr, nonce); + assert_ok!( + builder::eth_call(target_contract.addr) + .authorization_list(vec![auth1]) + .eth_gas_limit(crate::test_utils::ETH_GAS_LIMIT.into()) + .build() + ); + assert!(AccountInfo::::is_delegated(&authority)); + assert_eq!( + AccountInfo::::get_delegation_target(&authority), + Some(target_contract.addr) + ); + assert_eq!(frame_system::Pallet::::account_nonce(&authority_id), 1); + let hold_after_set = + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &authority_id); + assert!(hold_after_set > 0, "deposit should be held after delegation"); + + // Clear delegation via zero address + let new_nonce = U256::from(frame_system::Pallet::::account_nonce(&authority_id)); + let auth2 = signer.sign_authorization(chain_id, H160::zero(), new_nonce); + assert_ok!( + builder::eth_call(target_contract.addr) + .authorization_list(vec![auth2]) + .eth_gas_limit(crate::test_utils::ETH_GAS_LIMIT.into()) + .build() + ); + assert!(!AccountInfo::::is_delegated(&authority)); + assert_eq!(AccountInfo::::get_delegation_target(&authority), None); + assert!(!AccountInfo::::is_contract(&authority)); + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &authority_id), + 0, + "deposit should be fully released" + ); + }); +} + +/// Delegation set via authorization list allows calling the delegated address +/// in the same eth_call. Authorizations are processed before execution, so the +/// call body finds the delegation and executes the target contract's code. +#[test] +fn test_runtime_delegation_resolution() { + use alloy_core::sol_types::SolCall; + use pallet_revive_fixtures::{Counter, FixtureType, compile_module_with_type}; + + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let chain_id = U256::from(::ChainId::get()); + + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + ::FeeInfo::deposit_txfee(::Currency::issue(10_000_000_000)); + + let counter = + builder::bare_instantiate(Code::Upload(counter_code)).build_and_unwrap_contract(); + + let seed = H256::from([1u8; 32]); + let signer = TestSigner::new(&seed.0); + let authority = signer.address; + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 100_000_000); + + let nonce = U256::from(frame_system::Pallet::::account_nonce(&authority_id)); + let auth = signer.sign_authorization(chain_id, counter.addr, nonce); + let result = builder::eth_call(authority) + .authorization_list(vec![auth]) + .eth_gas_limit(crate::test_utils::ETH_GAS_LIMIT.into()) + .data(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .build(); + assert_ok!(result); + + assert!(AccountInfo::::is_delegated(&authority)); + assert_eq!(AccountInfo::::get_delegation_target(&authority), Some(counter.addr)); + + // Verify that the delegation resolves correctly: setNumber(42) writes to + // the authority's storage through the delegated Counter code. + let write_result = builder::bare_call(authority) + .data(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .build_and_unwrap_result(); + assert!(!write_result.did_revert()); + + let read_result = builder::bare_call(authority) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert!(!read_result.did_revert()); + assert_eq!(Counter::numberCall::abi_decode_returns(&read_result.data).unwrap(), 42u64); + }); +} + +/// Re-delegation to a different target preserves the same trie_id (storage persists). +/// +/// Per EIP-7702, storage is keyed by the delegated address, not the target. +/// This means switching from target A to target B retains target A's storage +/// in the same child trie. The spec recommends ERC-7201 namespaced storage to +/// avoid layout collisions. +#[test] +fn redelegation_preserves_storage() { + use alloy_core::sol_types::SolCall; + use pallet_revive_fixtures::{Counter, FixtureType, compile_module_with_type}; + + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + // Deploy two Counter instances as delegation targets + let counter_a = builder::bare_instantiate(Code::Upload(counter_code.clone())) + .build_and_unwrap_contract(); + let counter_b = builder::bare_instantiate(Code::Upload(counter_code)) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + + // Alice delegates to Counter A and writes storage + AccountInfo::::set_delegation(&ALICE_ADDR, counter_a.addr).unwrap(); + + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert()); + + // Verify storage was written + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert_eq!(Counter::numberCall::abi_decode_returns(&result.data).unwrap(), 42u64); + + // Re-delegate to Counter B (same ABI, same storage layout) + AccountInfo::::set_delegation(&ALICE_ADDR, counter_b.addr).unwrap(); + + // Storage from Counter A should still be accessible since the trie_id is + // derived from the delegated address, not the target + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + Counter::numberCall::abi_decode_returns(&result.data).unwrap(), + 42u64, + "Storage should persist across re-delegation" + ); + + // Counter B's increment should work on the same storage + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::incrementCall {}.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert()); + + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + Counter::numberCall::abi_decode_returns(&result.data).unwrap(), + 43u64, + "Increment via new target should work on persisted storage" + ); + }); +} + +/// After clearing a delegation, calling the address should not execute code. +/// +/// Even though contract_info (trie_id, deposit accounting) is preserved for +/// re-delegation, bare_call must not resolve code for a cleared delegation. +#[test] +fn cleared_delegation_does_not_execute_code() { + use alloy_core::sol_types::SolCall; + use pallet_revive_fixtures::{Counter, FixtureType, compile_module_with_type}; + + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let counter = + builder::bare_instantiate(Code::Upload(counter_code)).build_and_unwrap_contract(); + + // Delegate ALICE → Counter and write storage + AccountInfo::::set_delegation(&ALICE_ADDR, counter.addr).unwrap(); + + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert()); + + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert_eq!(Counter::numberCall::abi_decode_returns(&result.data).unwrap(), 42u64); + + // Clear delegation + AccountInfo::::clear_delegation(&ALICE_ADDR).unwrap(); + assert!(!AccountInfo::::is_delegated(&ALICE_ADDR)); + + // Calling number() should no longer execute Counter code + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert!(result.data.is_empty(), "cleared delegation should not execute code"); + }); +} + +/// dry_run_eth_transact with authorization list processes delegations and +/// includes the ED cost for new accounts in the gas estimate. +#[test] +fn dry_run_with_authorization_list() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000_000); + + let target_contract = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let chain_id = U256::from(::ChainId::get()); + let seed = H256::from([0xAA; 32]); + let signer = TestSigner::new(&seed.0); + + let authority_id = ::AddressMapper::to_account_id(&signer.address); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 100_000_000); + + let nonce = U256::from(frame_system::Pallet::::account_nonce(&authority_id)); + let auth = signer.sign_authorization(chain_id, target_contract.addr, nonce); + + // Dry run without authorization list + let baseline = crate::Pallet::::dry_run_eth_transact( + crate::GenericTransaction { + from: Some(ALICE_ADDR), + to: Some(target_contract.addr), + ..Default::default() + }, + Default::default(), + ); + assert_ok!(&baseline); + + // Dry run with authorization list + let with_auth = crate::Pallet::::dry_run_eth_transact( + crate::GenericTransaction { + from: Some(ALICE_ADDR), + to: Some(target_contract.addr), + authorization_list: vec![auth], + ..Default::default() + }, + Default::default(), + ); + assert_ok!(&with_auth); + + // The gas estimate with auth should be strictly greater since it includes ED cost + let baseline_gas = baseline.unwrap().eth_gas; + let auth_gas = with_auth.unwrap().eth_gas; + assert!( + auth_gas > baseline_gas, + "Auth gas ({auth_gas}) should be > baseline gas ({baseline_gas})" + ); + + // The delegation should have been applied during dry run + assert!(AccountInfo::::is_delegated(&signer.address)); + }); +} + +/// Test that delegation chains are not followed during execution (EIP-7702 spec) +/// +/// Per EIP-7702: "In case a delegation indicator points to another delegation, +/// creating a potential chain or loop of delegations, clients must retrieve +/// only the first code and then stop following the delegation chain." +/// +/// This test verifies: +/// 1. Calling Alice (who delegates to Counter) executes the contract code +/// 2. A contract can delegatecall to Alice and execute the contract code +/// 3. Calling Bob (who delegates to Alice) does NOT execute code (chain not followed) +#[test] +fn delegation_chain_does_not_execute() { + use alloy_core::sol_types::SolCall; + use pallet_revive_fixtures::{Caller, Counter, FixtureType, compile_module_with_type}; + + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + let (caller_code, _) = compile_module_with_type("Caller", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + let _ = <::Currency as Mutate<_>>::set_balance(&BOB, 100_000_000); + + // Deploy Counter contract + let counter = + builder::bare_instantiate(Code::Upload(counter_code)).build_and_unwrap_contract(); + + // Alice delegates to the Counter contract + AccountInfo::::set_delegation(&ALICE_ADDR, counter.addr).unwrap(); + + // Helper to read Alice's number storage slot + let read_number = || { + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::numberCall {}.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert()); + Counter::numberCall::abi_decode_returns(&result.data).unwrap() + }; + + // Case 1: Calling Alice executes the contract - setNumber(42) should work + let result = builder::bare_call(ALICE_ADDR) + .data(Counter::setNumberCall { newNumber: 42u64 }.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "calling Alice should execute Counter code"); + assert_eq!(read_number(), 42u64); + + // Case 2: A contract can delegatecall to Alice and execute the code + let caller_contract = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + let result = builder::bare_call(caller_contract.addr) + .data( + Caller::delegateCall { + _callee: ALICE_ADDR.0.into(), + _data: Counter::incrementCall {}.abi_encode().into(), + _gas: u64::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "delegatecall to Alice should work"); + let decoded = Caller::delegateCall::abi_decode_returns(&result.data).unwrap(); + assert!(decoded.success, "delegatecall to Alice should succeed"); + // delegatecall modifies the caller's storage, not Alice's + assert_eq!(read_number(), 42u64); + + // Case 3: Bob delegates to Alice (chain: Bob -> Alice -> Counter) + // Calling Bob should NOT execute code because chains are not followed + AccountInfo::::set_delegation(&BOB_ADDR, ALICE_ADDR).unwrap(); + + let result = builder::bare_call(BOB_ADDR) + .data(Counter::setNumberCall { newNumber: 99u64 }.abi_encode()) + .build_and_unwrap_result(); + // Bob is treated as an EOA (no code), so the call succeeds but does nothing + assert!(!result.did_revert(), "call to Bob should not revert (treated as EOA transfer)"); + assert!(result.data.is_empty(), "call to Bob should return empty (no code executed)"); + // Alice's number should be unchanged + assert_eq!(read_number(), 42u64); + }); +} + +/// Delegation to a nonexistent address (no deployed code) results in a no-op call. +/// The authority is treated as an EOA with no executable code. +#[test] +fn delegation_to_nonexistent_address_is_noop() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + // Delegate Alice to an address that has no deployed contract + let nonexistent = H160::from([0xDE; 20]); + AccountInfo::::set_delegation(&ALICE_ADDR, nonexistent).unwrap(); + assert!(AccountInfo::::is_delegated(&ALICE_ADDR)); + assert_eq!(AccountInfo::::get_delegation_target(&ALICE_ADDR), Some(nonexistent)); + + // Calling Alice should succeed but execute no code (empty return data) + let result = builder::bare_call(ALICE_ADDR) + .data(vec![0xDE, 0xAD, 0xBE, 0xEF]) // arbitrary calldata + .build_and_unwrap_result(); + assert!(!result.did_revert(), "call should not revert"); + assert!( + result.data.is_empty(), + "no code should execute for delegation to nonexistent address" + ); + + // eth_getCode should still return the delegation indicator + let mut expected_code = vec![0xef, 0x01, 0x00]; + expected_code.extend_from_slice(nonexistent.as_bytes()); + assert_eq!(crate::Pallet::::code(&ALICE_ADDR), expected_code); + }); +} + +/// SELFDESTRUCT on a delegated account transfers the account's balance to the +/// beneficiary but does NOT remove the delegation indicator or affect the +/// original contract. Per EIP-6780, selfdestruct only clears the account when +/// called in the same transaction as creation, which is not the case here. +/// +/// Test flow: +/// 1. Deploy Terminate contract +/// 2. Fund Alice and delegate her to the Terminate contract +/// 3. Call destroy(beneficiary) on Alice — runs in Alice's context +/// 4. Verify: Alice balance → 0, beneficiary received funds +/// 5. Verify: delegation indicator survives (eth_getCode still returns 0xef0100||addr) +/// 6. Verify: original contract code unaffected +/// 7. Verify: delegation still functional (echo(42) returns 42) +#[test] +fn selfdestruct_on_delegated_account() { + use alloy_core::sol_types::{SolCall, SolConstructor}; + use pallet_revive_fixtures::{FixtureType, Terminate, compile_module_with_type}; + + let (code, _) = compile_module_with_type("Terminate", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + // Deploy the Terminate contract (skip=true to not selfdestruct in constructor) + let contract = builder::bare_instantiate(Code::Upload(code)) + .constructor_data( + Terminate::constructorCall { + skip: true, + method: 0, + beneficiary: H160::zero().0.into(), + } + .abi_encode(), + ) + .build_and_unwrap_contract(); + + // Fund Alice and delegate her to the Terminate contract + let alice_balance = 5_000_000u128; + let alice_id = ::AddressMapper::to_account_id(&ALICE_ADDR); + let _ = <::Currency as Mutate<_>>::set_balance(&alice_id, alice_balance); + AccountInfo::::set_delegation(&ALICE_ADDR, contract.addr).unwrap(); + assert!(AccountInfo::::is_delegated(&ALICE_ADDR)); + + // Beneficiary must exist (has at least ED) so the selfdestruct balance + // transfer doesn't need to charge ED from the origin (which is Alice + // herself in the delegated case). + let beneficiary = DJANGO_ADDR; + let beneficiary_id = ::AddressMapper::to_account_id(&beneficiary); + let min_balance = Contracts::min_balance(); + let _ = + <::Currency as Mutate<_>>::set_balance(&beneficiary_id, min_balance); + + // Save contract code before selfdestruct for later comparison + let contract_code_before = crate::Pallet::::code(&contract.addr); + assert!(!contract_code_before.is_empty()); + + // Step 2: Call destroy(beneficiary) on Alice — selfdestruct runs in Alice's context + let result = builder::bare_call(ALICE_ADDR) + .data( + Terminate::terminateCall { + method: 2, // METHOD_SYSCALL = selfdestruct opcode + beneficiary: beneficiary.0.into(), + } + .abi_encode(), + ) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "selfdestruct should succeed"); + + // Check balances — Alice's balance transferred to beneficiary. + // EIP-6780: selfdestruct only sends balance, doesn't delete account + // (account was not created in this transaction). + let alice_balance_after = ::Currency::free_balance(&alice_id); + let beneficiary_balance_after = ::Currency::free_balance(&beneficiary_id); + assert_eq!( + beneficiary_balance_after, + min_balance + alice_balance - alice_balance_after, + "beneficiary should have received Alice's transferable balance" + ); + + // Step 4: Delegation indicator survives selfdestruct + assert!( + AccountInfo::::is_delegated(&ALICE_ADDR), + "delegation should survive selfdestruct" + ); + assert_eq!( + AccountInfo::::get_delegation_target(&ALICE_ADDR), + Some(contract.addr), + "delegation target should be unchanged" + ); + + // eth_getCode(alice) should still return the delegation indicator + let mut expected_code = vec![0xef, 0x01, 0x00]; + expected_code.extend_from_slice(contract.addr.as_bytes()); + assert_eq!(crate::Pallet::::code(&ALICE_ADDR), expected_code); + + // Step 5: Original contract is completely unaffected + let contract_code_after = crate::Pallet::::code(&contract.addr); + assert_eq!( + contract_code_before, contract_code_after, + "original contract code should be unchanged" + ); + + // Step 6: Delegation still functional — echo(42) returns 42 + // Fund Alice again so we can make a call + let _ = <::Currency as Mutate<_>>::set_balance(&alice_id, 1_000_000); + let expected = alloy_core::primitives::U256::from(42u64); + let result = builder::bare_call(ALICE_ADDR) + .data(Terminate::echoCall { value: expected }.abi_encode()) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "delegation should still be functional after selfdestruct"); + let returned = Terminate::echoCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!(returned, expected, "echo should return 42"); + }); +} + +/// Delegating to a contract charges a storage deposit; clearing refunds it. +#[test] +fn delegation_deposit_lifecycle() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let target = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let setup = DelegationTestSetup::new([0xCC; 32]); + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &setup.authority_id), + 0 + ); + + // Set delegation → deposit charged + setup.authorize(target.addr); + let hold = + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &setup.authority_id); + assert!(hold > 0, "should have a storage deposit hold after delegation"); + assert_eq!(hold, get_contract(&setup.signer.address).storage_base_deposit()); + + // Clear delegation → deposit refunded + let auth = setup.sign_authorization(H160::zero()); + setup.process(&[auth]); + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &setup.authority_id), + 0, + "hold should be fully released after clearing delegation" + ); + assert!(get_contract_checked(&setup.signer.address).is_none()); + }); +} + +/// Re-delegating to a different contract adjusts the deposit correctly. +#[test] +fn redelegation_adjusts_deposit() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + // Both targets use the same code, so deposits should be equal + let target_a = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + let target_b = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + + let setup = DelegationTestSetup::new([0xEE; 32]); + setup.authorize(target_a.addr); + + let hold_a = + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &setup.authority_id); + assert!(hold_a > 0); + + // Re-delegate to target B (same code size → same deposit) + let auth = setup.sign_authorization(target_b.addr); + setup.process(&[auth]); + + let hold_b = + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &setup.authority_id); + assert_eq!(hold_a, hold_b, "same-code re-delegation should keep the same deposit"); + assert_eq!( + AccountInfo::::get_delegation_target(&setup.signer.address), + Some(target_b.addr) + ); + }); +} + +/// Delegation to a contract increments its code refcount; clearing decrements it. +/// Re-delegation to the same target does not change the refcount. +#[test] +fn delegation_manages_code_refcount() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let target = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let code_hash = get_contract(&target.addr).code_hash; + let refcount_before = CodeInfoOf::::get(code_hash).unwrap().refcount(); + + let authority = H160::from([0x11; 20]); + + // Set delegation → refcount++ + AccountInfo::::set_delegation(&authority, target.addr).unwrap(); + assert_eq!(CodeInfoOf::::get(code_hash).unwrap().refcount(), refcount_before + 1); + + // Re-delegate to same target → refcount unchanged + AccountInfo::::set_delegation(&authority, target.addr).unwrap(); + assert_eq!(CodeInfoOf::::get(code_hash).unwrap().refcount(), refcount_before + 1); + + // Clear delegation → refcount-- + AccountInfo::::clear_delegation(&authority).unwrap(); + assert_eq!(CodeInfoOf::::get(code_hash).unwrap().refcount(), refcount_before); + }); +} + +/// Re-delegation from contract A to contract B decrements A's refcount and increments B's. +#[test] +fn redelegation_updates_refcounts() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let target_a = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + // Deploy a different contract so it has a different code hash + use pallet_revive_fixtures::{FixtureType, compile_module_with_type}; + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + let target_b = + builder::bare_instantiate(Code::Upload(counter_code)).build_and_unwrap_contract(); + + let hash_a = get_contract(&target_a.addr).code_hash; + let hash_b = get_contract(&target_b.addr).code_hash; + assert_ne!(hash_a, hash_b); + + let refcount_a_before = CodeInfoOf::::get(hash_a).unwrap().refcount(); + let refcount_b_before = CodeInfoOf::::get(hash_b).unwrap().refcount(); + + let authority = H160::from([0x11; 20]); + + // Delegate to A + AccountInfo::::set_delegation(&authority, target_a.addr).unwrap(); + assert_eq!(CodeInfoOf::::get(hash_a).unwrap().refcount(), refcount_a_before + 1); + + // Re-delegate to B + AccountInfo::::set_delegation(&authority, target_b.addr).unwrap(); + assert_eq!( + CodeInfoOf::::get(hash_a).unwrap().refcount(), + refcount_a_before, + "old code refcount should be decremented" + ); + assert_eq!( + CodeInfoOf::::get(hash_b).unwrap().refcount(), + refcount_b_before + 1, + "new code refcount should be incremented" + ); + }); +} + +/// Re-delegation from contract → EOA → contract must not double-decrement the original +/// code's refcount or double-refund the deposit. Verifies both refcounts and returned +/// deposit values at each step. +#[test] +fn redelegation_via_eoa_does_not_double_decrement() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let target_a = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + use pallet_revive_fixtures::{FixtureType, compile_module_with_type}; + let (counter_code, _) = compile_module_with_type("Counter", FixtureType::Solc).unwrap(); + let target_c = + builder::bare_instantiate(Code::Upload(counter_code)).build_and_unwrap_contract(); + + let hash_a = get_contract(&target_a.addr).code_hash; + let hash_c = get_contract(&target_c.addr).code_hash; + assert_ne!(hash_a, hash_c); + + let refcount_a_before = CodeInfoOf::::get(hash_a).unwrap().refcount(); + let refcount_c_before = CodeInfoOf::::get(hash_c).unwrap().refcount(); + + let authority = H160::from([0x11; 20]); + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 100_000_000); + + // Step 1: delegate to contract A — charges deposit, increments refcount + let deposit_a = AccountInfo::::set_delegation(&authority, target_a.addr).unwrap(); + let charge_a = match deposit_a { + crate::StorageDeposit::Charge(d) => d, + other => panic!("expected Charge, got {other:?}"), + }; + assert!(charge_a > 0, "delegation to contract should charge a deposit"); + assert_eq!(CodeInfoOf::::get(hash_a).unwrap().refcount(), refcount_a_before + 1); + + // Step 2: re-delegate to a plain EOA — refunds the full deposit, decrements refcount + let plain_eoa = H160::from([0x77; 20]); + let deposit_eoa = AccountInfo::::set_delegation(&authority, plain_eoa).unwrap(); + assert_eq!( + deposit_eoa, + crate::StorageDeposit::Refund(charge_a), + "re-delegating to EOA should refund the full deposit from step 1" + ); + assert_eq!( + CodeInfoOf::::get(hash_a).unwrap().refcount(), + refcount_a_before, + "A's refcount should be back to original after re-delegating to EOA" + ); + + // Step 3: re-delegate to contract C — charges a fresh deposit, must NOT touch A + let deposit_c = AccountInfo::::set_delegation(&authority, target_c.addr).unwrap(); + let charge_c = match deposit_c { + crate::StorageDeposit::Charge(d) => d, + other => panic!("expected Charge, got {other:?}"), + }; + assert!(charge_c > 0, "delegation to contract should charge a deposit"); + assert_eq!( + CodeInfoOf::::get(hash_a).unwrap().refcount(), + refcount_a_before, + "A's refcount must not be decremented again" + ); + assert_eq!( + CodeInfoOf::::get(hash_c).unwrap().refcount(), + refcount_c_before + 1, + "C's refcount should be incremented" + ); + }); +} + +/// Delegating to a non-contract (plain EOA) does not create a contract_info or charge a deposit. +#[test] +fn delegation_to_eoa_has_no_deposit() { + ExtBuilder::default().build().execute_with(|| { + let authority = H160::from([0x11; 20]); + let plain_eoa = H160::from([0x22; 20]); + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = <::Currency as Mutate<_>>::set_balance(&authority_id, 100_000_000); + + let deposit = AccountInfo::::set_delegation(&authority, plain_eoa).unwrap(); + + assert!(AccountInfo::::is_delegated(&authority)); + assert!(deposit.is_zero(), "delegation to EOA should not charge any deposit"); + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &authority_id), + 0 + ); + // No contract info created + assert!(get_contract_checked(&authority).is_none()); + }); +} + +/// Multiple delegations from different authorities to the same contract each get their own deposit. +#[test] +fn multiple_delegations_each_have_own_deposit() { + ExtBuilder::default().build().execute_with(|| { + let _ = <::Currency as Mutate<_>>::set_balance(&ALICE, 100_000_000); + + let target = builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let authority_a = H160::from([0x11; 20]); + let authority_b = H160::from([0x22; 20]); + let id_a = ::AddressMapper::to_account_id(&authority_a); + let id_b = ::AddressMapper::to_account_id(&authority_b); + let _ = <::Currency as Mutate<_>>::set_balance(&id_a, 100_000_000); + let _ = <::Currency as Mutate<_>>::set_balance(&id_b, 100_000_000); + + // Delegate both to the same target + let deposit_a = AccountInfo::::set_delegation(&authority_a, target.addr).unwrap(); + let deposit_b = AccountInfo::::set_delegation(&authority_b, target.addr).unwrap(); + + // Both should get the same charge since they delegate to the same code + assert_eq!(deposit_a, deposit_b); + + // Each authority has independent contract info + let ci_a = get_contract(&authority_a); + let ci_b = get_contract(&authority_b); + assert_eq!(ci_a.storage_base_deposit(), ci_b.storage_base_deposit()); + // But different trie_ids (storage is per-delegator) + assert_ne!(ci_a.child_trie_info(), ci_b.child_trie_info()); + }); +} diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 2b72c600ef09a..a5876ada34f74 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -174,6 +174,45 @@ fn basic_evm_flow_tracing_works() { }); } +/// Calling a delegated EOA should trace as CallType::Call (not DelegateCall). +#[test] +fn delegated_eoa_call_tracing_works() { + use crate::{ + evm::{CallTrace, CallTracer, CallType}, + tests::{dummy_evm_contract, eip7702::DelegationTestSetup}, + }; + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr: target_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let setup = DelegationTestSetup::default(); + setup.authorize(target_addr); + + let mut tracer = CallTracer::new(Default::default()); + let _ = trace(&mut tracer, || { + builder::bare_call(setup.signer.address).build_and_unwrap_result() + }); + + let call_trace = tracer.collect_trace().unwrap(); + assert_eq!( + call_trace, + CallTrace { + call_type: CallType::Call, + from: ALICE_ADDR, + to: setup.signer.address, + value: Some(crate::U256::zero()), + gas: call_trace.gas, + gas_used: call_trace.gas_used, + ..Default::default() + } + ); + }); +} + /// Regression test for paritytech/contract-issues#278 — nested-call variant. /// /// `Stack::call`'s no-code branch (the path taken when a running contract diff --git a/substrate/frame/revive/src/tests/sol/host.rs b/substrate/frame/revive/src/tests/sol/host.rs index d31ed43c56e40..48932adbe6fda 100644 --- a/substrate/frame/revive/src/tests/sol/host.rs +++ b/substrate/frame/revive/src/tests/sol/host.rs @@ -17,18 +17,30 @@ //! The pallet-revive shared VM integration test suite. use crate::{ - Code, Config, Error, H256, Key, System, U256, + Code, Config, Error, H256, Key, PristineCode, System, U256, address::AddressMapper, + evm::fees::InfoT, + exec::EMPTY_CODE_HASH, metering::TransactionLimits, - test_utils::{ALICE, BOB, BOB_ADDR, builder::Contract}, - tests::{ExtBuilder, RuntimeEvent, Test, builder, test_utils, test_utils::get_contract}, + storage::AccountInfo, + test_utils::{ + ALICE, BOB, BOB_ADDR, CHARLIE, CHARLIE_ADDR, DJANGO, DJANGO_ADDR, builder::Contract, + }, + tests::{ + Contracts, ExtBuilder, RuntimeEvent, Test, TestSigner, builder, dummy_evm_contract, + test_utils, test_utils::get_contract, + }, }; -use frame_support::assert_err_ignore_postinfo; +use frame_support::{assert_err_ignore_postinfo, assert_ok}; use alloy_core::sol_types::{SolCall, SolInterface}; -use frame_support::traits::{Get, fungible::Mutate}; +use frame_support::traits::{ + Get, + fungible::{Balanced, Mutate}, +}; use pallet_revive_fixtures::{Caller, FixtureType, Host, compile_module_with_type}; use pretty_assertions::assert_eq; +use sp_core::H160; use test_case::test_case; fn convert_to_free_balance(total_balance: u128) -> U256 { @@ -38,6 +50,62 @@ fn convert_to_free_balance(total_balance: u128) -> U256 { U256::from((total_balance - existential_deposit_planck) * native_to_eth) } +/// Create a delegated EOA that points to the given target contract +fn create_delegated_eoa(target: &H160) -> H160 { + use core::sync::atomic::{AtomicU32, Ordering}; + static COUNTER: AtomicU32 = AtomicU32::new(1); + let chain_id = U256::from(::ChainId::get()); + let mut seed_bytes = [0u8; 32]; + seed_bytes[..4].copy_from_slice(&COUNTER.fetch_add(1, Ordering::Relaxed).to_le_bytes()); + let seed = H256::from(seed_bytes); + let signer = TestSigner::new(&seed.0); + let authority = signer.address; + + let authority_id = ::AddressMapper::to_account_id(&authority); + let _ = ::Currency::set_balance(&authority_id, 100_000_000); + + // Pre-fund the tx fee pool so charge_deposit can withdraw from it + ::FeeInfo::deposit_txfee(::Currency::issue(10_000_000_000)); + + let nonce = U256::from(frame_system::Pallet::::account_nonce(&authority_id)); + let auth = signer.sign_authorization(chain_id, *target, nonce); + + // Process the authorization to set up delegation + let result = builder::eth_call(*target) + .authorization_list(vec![auth]) + .eth_gas_limit(crate::test_utils::ETH_GAS_LIMIT.into()) + .build(); + assert_ok!(result); + + assert!(AccountInfo::::is_delegated(&authority)); + authority +} + +/// Call EXTCODEHASH opcode via the Host contract +fn call_extcodehash(host: &H160, target: &H160) -> H256 { + let result = builder::bare_call(*host) + .data( + Host::HostCalls::extcodehashOp(Host::extcodehashOpCall { account: target.0.into() }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "extcodehash call reverted"); + let decoded = Host::extcodehashOpCall::abi_decode_returns(&result.data).unwrap(); + H256::from_slice(decoded.as_slice()) +} + +/// Call EXTCODESIZE opcode via the Host contract +fn call_extcodesize(host: &H160, target: &H160) -> u64 { + let result = builder::bare_call(*host) + .data( + Host::HostCalls::extcodesizeOp(Host::extcodesizeOpCall { account: target.0.into() }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert!(!result.did_revert(), "extcodesize call reverted"); + Host::extcodesizeOpCall::abi_decode_returns(&result.data).unwrap() +} + #[test_case(FixtureType::Solc)] #[test_case(FixtureType::Resolc)] fn balance_works(fixture_type: FixtureType) { @@ -112,33 +180,37 @@ fn extcodesize_works(fixture_type: FixtureType) { ExtBuilder::default().build().execute_with(|| { ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = + let Contract { addr: host_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let expected_code_size = { - let contract_info = test_utils::get_contract(&addr); - let code_hash = contract_info.code_hash; - U256::from(test_utils::ensure_stored(code_hash)) + let Contract { addr: target_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let host_code_size = { + let info = test_utils::get_contract(&host_addr); + test_utils::ensure_stored(info.code_hash) as u64 }; - { - let result = builder::bare_call(addr) - .data( - Host::HostCalls::extcodesizeOp(Host::extcodesizeOpCall { - account: addr.0.into(), - }) - .abi_encode(), - ) - .build_and_unwrap_result(); - assert!(!result.did_revert(), "test reverted"); + ::Currency::set_balance(&CHARLIE, 100_000_000); + let delegated_eoa = create_delegated_eoa(&target_addr); - let decoded = Host::extcodesizeOpCall::abi_decode_returns(&result.data).unwrap(); + struct TestCase { + name: &'static str, + addr: H160, + expected: u64, + } - assert_eq!( - expected_code_size.as_u64(), - decoded, - "EXTCODESIZE should return the code size for {fixture_type:?}", - ); + let cases = vec![ + TestCase { name: "regular contract", addr: host_addr, expected: host_code_size }, + TestCase { name: "delegated EOA", addr: delegated_eoa, expected: 23 }, + TestCase { name: "regular EOA", addr: CHARLIE_ADDR, expected: 0 }, + TestCase { name: "non-existent", addr: H160::from_low_u64_be(0xdead), expected: 0 }, + ]; + + for TestCase { name, addr, expected } in cases { + let result = call_extcodesize(&host_addr, &addr); + assert_eq!(result, expected, "EXTCODESIZE for {name} failed"); } }); } @@ -151,32 +223,124 @@ fn extcodehash_works(fixture_type: FixtureType) { ExtBuilder::default().build().execute_with(|| { ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = + // Deploy the Host contract (used to call EXTCODEHASH) + let Contract { addr: host_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let expected_code_hash = { - let contract_info = test_utils::get_contract(&addr); - contract_info.code_hash + // Deploy a target contract for delegation tests + let Contract { addr: target_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let host_code_hash = test_utils::get_contract(&host_addr).code_hash; + struct TestCase { + name: &'static str, + addr: H160, + expected: H256, + } + + ::Currency::set_balance(&DJANGO, 100_000_000); + ::Currency::set_balance(&CHARLIE, 100_000_000); + + let delegated_eoa = create_delegated_eoa(&target_addr); + let chained_delegated_eoa = create_delegated_eoa(&target_addr); + AccountInfo::::set_delegation(&DJANGO_ADDR, chained_delegated_eoa).unwrap(); + + // EIP-7702: delegated EOAs return keccak256(0xef0100 || target) + let delegation_indicator_hash = |target: &H160| -> H256 { + sp_io::hashing::keccak_256(&AccountInfo::::delegation_indicator(target)).into() }; - { - let result = builder::bare_call(addr) - .data( - Host::HostCalls::extcodehashOp(Host::extcodehashOpCall { - account: addr.0.into(), - }) - .abi_encode(), - ) - .build_and_unwrap_result(); - assert!(!result.did_revert(), "test reverted"); + let cases = vec![ + TestCase { name: "regular contract", addr: host_addr, expected: host_code_hash }, + TestCase { + name: "delegated EOA", + addr: delegated_eoa, + expected: delegation_indicator_hash(&target_addr), + }, + TestCase { + name: "delegation chain", + addr: DJANGO_ADDR, + expected: delegation_indicator_hash(&chained_delegated_eoa), + }, + TestCase { name: "regular EOA", addr: CHARLIE_ADDR, expected: EMPTY_CODE_HASH }, + TestCase { + name: "non-existent", + addr: H160::from_low_u64_be(0xdead), + expected: H256::zero(), + }, + ]; - let decoded = Host::extcodehashOpCall::abi_decode_returns(&result.data).unwrap(); + for TestCase { name, addr, expected } in cases { + let result = call_extcodehash(&host_addr, &addr); + assert_eq!(result, expected, "EXTCODEHASH for {name} failed"); + } + }); +} - assert_eq!( - expected_code_hash, - H256::from_slice(decoded.as_slice()), - "EXTCODEHASH should return the code hash for {fixture_type:?}", - ); +#[test] +fn pallet_code_works() { + ExtBuilder::default().build().execute_with(|| { + ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr: contract_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_evm_contract())) + .build_and_unwrap_contract(); + + let pristine_code = + PristineCode::::get(test_utils::get_contract(&contract_addr).code_hash) + .unwrap() + .to_vec(); + + ::Currency::set_balance(&CHARLIE, 100_000_000); + let delegated_eoa = create_delegated_eoa(&contract_addr); + + enum Expected { + Code(Vec), + Delegated(H160), + Empty, + } + + struct TestCase { + name: &'static str, + addr: H160, + expected: Expected, + } + + let cases = vec![ + TestCase { + name: "contract", + addr: contract_addr, + expected: Expected::Code(pristine_code), + }, + TestCase { + name: "delegated EOA", + addr: delegated_eoa, + expected: Expected::Delegated(contract_addr), + }, + TestCase { name: "regular EOA", addr: CHARLIE_ADDR, expected: Expected::Empty }, + TestCase { + name: "non-existent", + addr: H160::from_low_u64_be(0xdead), + expected: Expected::Empty, + }, + ]; + + for TestCase { name, addr, expected } in cases { + let code = Contracts::code(&addr); + match expected { + Expected::Code(expected) => { + assert_eq!(code, expected, "Pallet::code for {name} failed"); + }, + Expected::Delegated(target) => { + let mut expected = vec![0xef, 0x01, 0x00]; + expected.extend_from_slice(target.as_bytes()); + assert_eq!(code, expected, "Pallet::code for {name} failed"); + }, + Expected::Empty => { + assert!(code.is_empty(), "Pallet::code for {name} should be empty"); + }, + } } }); } @@ -204,30 +368,42 @@ fn extcodecopy_works(caller_type: FixtureType, callee_type: FixtureType) { .map(|bounded_vec| bounded_vec.to_vec()) .unwrap_or_default(); + let delegated_eoa = create_delegated_eoa(&dummy_addr); + let indicator = AccountInfo::::delegation_indicator(&dummy_addr).to_vec(); + struct TestCase { description: &'static str, + target: H160, offset: usize, size: usize, expected: Vec, } - // Test cases covering different scenarios let test_cases = vec![ TestCase { - description: "copy within bounds", + description: "contract: copy within bounds", + target: dummy_addr, offset: 3, size: 17, expected: full_code[3..20].to_vec(), }, - TestCase { description: "len = 0", offset: 0, size: 0, expected: vec![] }, TestCase { - description: "offset beyond code length", + description: "contract: len = 0", + target: dummy_addr, + offset: 0, + size: 0, + expected: vec![], + }, + TestCase { + description: "contract: offset beyond code length", + target: dummy_addr, offset: full_code.len(), size: 10, expected: vec![0u8; 10], }, TestCase { - description: "offset + size beyond code", + description: "contract: offset + size beyond code", + target: dummy_addr, offset: full_code.len().saturating_sub(5), size: 20, expected: { @@ -237,7 +413,8 @@ fn extcodecopy_works(caller_type: FixtureType, callee_type: FixtureType) { }, }, TestCase { - description: "size larger than remaining", + description: "contract: size larger than remaining", + target: dummy_addr, offset: 10, size: full_code.len(), expected: { @@ -246,31 +423,56 @@ fn extcodecopy_works(caller_type: FixtureType, callee_type: FixtureType) { expected }, }, + TestCase { + description: "delegated EOA: full indicator", + target: delegated_eoa, + offset: 0, + size: 23, + expected: indicator.clone(), + }, + TestCase { + description: "delegated EOA: prefix only", + target: delegated_eoa, + offset: 0, + size: 3, + expected: vec![0xef, 0x01, 0x00], + }, + TestCase { + description: "delegated EOA: partial at end, zero-padded", + target: delegated_eoa, + offset: 20, + size: 10, + expected: { + let mut expected = vec![0u8; 10]; + expected[..3].copy_from_slice(&indicator[20..23]); + expected + }, + }, + TestCase { + description: "delegated EOA: offset beyond indicator", + target: delegated_eoa, + offset: 23, + size: 5, + expected: vec![0u8; 5], + }, ]; - for test_case in test_cases { + for TestCase { description, target, offset, size, expected } in test_cases { let result = builder::bare_call(addr) .data( HostEvmOnlyCalls::extcodecopyOp(HostEvmOnly::extcodecopyOpCall { - account: dummy_addr.0.into(), - offset: test_case.offset as u64, - size: test_case.size as u64, + account: target.0.into(), + offset: offset as u64, + size: size as u64, }) .abi_encode(), ) .build_and_unwrap_result(); + assert!(!result.did_revert(), "reverted: {}", description); - assert!(!result.did_revert(), "test reverted for: {}", test_case.description); - - let return_value = HostEvmOnly::extcodecopyOpCall::abi_decode_returns(&result.data) - .expect("Failed to decode extcodecopyOp return value"); - let actual_code = &return_value.0; - - assert_eq!( - &test_case.expected, actual_code, - "EXTCODECOPY content mismatch for {}", - test_case.description - ); + let actual = + HostEvmOnly::extcodecopyOpCall::abi_decode_returns(&result.data).unwrap().0; + assert_eq!(expected, actual.to_vec(), "EXTCODECOPY mismatch: {}", description); } }); } diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 45fd480ff0a1f..cde02fc5724ce 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -59,6 +59,8 @@ fn keccak_256_works(fixture_type: FixtureType) { #[test_case(FixtureType::Solc)] #[test_case(FixtureType::Resolc)] fn address_works(fixture_type: FixtureType) { + use crate::tests::eip7702::DelegationTestSetup; + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); @@ -72,6 +74,17 @@ fn address_works(fixture_type: FixtureType) { let decoded = SystemFixture::addressFuncCall::abi_decode_returns(&result.data).unwrap(); assert_eq!(addr, H160::from_slice(decoded.as_slice())); + + // EIP-7702: ADDRESS in delegated code returns the EOA's address, not the contract's + let setup = DelegationTestSetup::default(); + setup.authorize(addr); + + let result = builder::bare_call(setup.signer.address) + .data(SystemFixture::addressFuncCall {}.abi_encode()) + .build_and_unwrap_result(); + + let decoded = SystemFixture::addressFuncCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!(setup.signer.address, H160::from_slice(decoded.as_slice())); }); } diff --git a/substrate/frame/revive/src/tests/sol/terminate.rs b/substrate/frame/revive/src/tests/sol/terminate.rs index 15b9435a1aafa..9fde32277ab47 100644 --- a/substrate/frame/revive/src/tests/sol/terminate.rs +++ b/substrate/frame/revive/src/tests/sol/terminate.rs @@ -698,3 +698,43 @@ fn call_after_terminate_works(fixture_type: FixtureType, method: u8) { assert_eq!(get_balance(&DJANGO), 0, "unexpected DJANGO balance after terminate"); }); } + +/// The system precompile's `terminate` must not destroy a delegated EOA. +/// Only the EVM SELFDESTRUCT opcode (with EIP-6780 semantics) is allowed. +#[test] +fn precompile_blocked_for_delegated_eoa() { + use crate::{storage::AccountInfo, tests::eip7702::DelegationTestSetup}; + + let (code, _) = compile_module_with_type("Terminate", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + + let contract = builder::bare_instantiate(Code::Upload(code)) + .constructor_data( + Terminate::constructorCall { + skip: true, + method: METHOD_PRECOMPILE, + beneficiary: DJANGO_ADDR.0.into(), + } + .abi_encode(), + ) + .build_and_unwrap_contract(); + + let setup = DelegationTestSetup::default(); + setup.authorize(contract.addr); + + // Call terminate via the system precompile — should revert + let result = builder::bare_call(setup.signer.address) + .data( + Terminate::terminateCall { + method: METHOD_PRECOMPILE, + beneficiary: DJANGO_ADDR.0.into(), + } + .abi_encode(), + ) + .build_and_unwrap_result(); + assert!(result.did_revert(), "terminate precompile should revert for delegated EOA"); + assert!(AccountInfo::::is_delegated(&setup.signer.address)); + }); +} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 3fb173f6a4a8c..42c80c77a9dc7 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -74,13 +74,16 @@ pub trait Tracing { /// Called before a contract call is executed. /// - /// For CALL/DELEGATECALL opcodes: + /// - `code_address`: When code is loaded from a different address than `to` (DELEGATECALL or + /// EIP-7702 delegation), this is that source address. + /// - `is_delegate_call`: true for DELEGATECALL frames (not EIP-7702 delegation). /// - `gas_limit`: gas forwarded to the child call fn enter_child_span( &mut self, _from: H160, _to: H160, - _delegate_call: Option, + _code_address: Option, + _is_delegate_call: bool, _is_read_only: bool, _value: U256, _input: &[u8], diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index 80fd6fb61d54a..9f2f6cf40bf06 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -178,6 +178,8 @@ pub enum RuntimeCosts { Blake2F(u32), /// Weight of calling `Modexp` precompile Modexp(u64), + /// Weight of processing EIP-7702 authorization tuples. + Delegations { new_accounts: u32, existing_accounts: u32 }, } /// For functions that modify storage, benchmarks are performed with one item in the @@ -340,6 +342,11 @@ impl Token for RuntimeCosts { Identity(len) => T::WeightInfo::identity(len), Blake2F(rounds) => T::WeightInfo::blake2f(rounds), Modexp(gas) => Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0), + Delegations { new_accounts, existing_accounts } => { + T::WeightInfo::process_new_account_authorization(new_accounts).saturating_add( + T::WeightInfo::process_existing_account_authorization(existing_accounts), + ) + }, } } } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 4e64ae5de4e1c..c2a87adb3652c 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -71,6 +71,8 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_revive`. pub trait WeightInfo { + fn process_new_account_authorization(n: u32, ) -> Weight; + fn process_existing_account_authorization(n: u32, ) -> Weight; fn deletion_queue_batch() -> Weight; fn deletion_queue_per_entry() -> Weight; fn deletion_queue_per_trie_key(k: u32, ) -> Weight; @@ -183,6 +185,21 @@ pub trait WeightInfo { /// Weights for `pallet_revive` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // TODO: regenerate via `/cmd bench --runtime dev --pallet pallet_revive` once the + // post-master-merge tree compiles. Placeholder weights below are conservative + // upper bounds derived from comparable benches; safe to over-charge. + fn process_new_account_authorization(n: u32, ) -> Weight { + Weight::from_parts(30_000_000, 0) + .saturating_add(Weight::from_parts(20_000_000, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } + fn process_existing_account_authorization(n: u32, ) -> Weight { + Weight::from_parts(20_000_000, 0) + .saturating_add(Weight::from_parts(10_000_000, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:0) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) fn deletion_queue_batch() -> Weight { @@ -1607,6 +1624,19 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests. impl WeightInfo for () { + // TODO: regenerate via `/cmd bench --runtime dev --pallet pallet_revive`. + fn process_new_account_authorization(n: u32, ) -> Weight { + Weight::from_parts(30_000_000, 0) + .saturating_add(Weight::from_parts(20_000_000, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } + fn process_existing_account_authorization(n: u32, ) -> Weight { + Weight::from_parts(20_000_000, 0) + .saturating_add(Weight::from_parts(10_000_000, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:0) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) fn deletion_queue_batch() -> Weight {