diff --git a/Cargo.lock b/Cargo.lock index a718a46379..20356094bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5991,7 +5991,6 @@ dependencies = [ "pallet-assets", "pallet-aura", "pallet-balances", - "pallet-base-fee", "pallet-block-reward", "pallet-chain-extension-assets", "pallet-chain-extension-dapps-staking", @@ -6001,6 +6000,7 @@ dependencies = [ "pallet-contracts-primitives", "pallet-dapps-staking", "pallet-democracy", + "pallet-dynamic-evm-base-fee", "pallet-ethereum", "pallet-ethereum-checked", "pallet-evm", @@ -6033,6 +6033,7 @@ dependencies = [ "pallet-xvm", "parity-scale-codec", "scale-info", + "smallvec 1.11.0", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -7666,6 +7667,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-dynamic-evm-base-fee" +version = "0.1.0" +dependencies = [ + "fp-evm", + "frame-benchmarking", + "frame-support", + "frame-system", + "num-traits", + "pallet-balances", + "pallet-timestamp", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", +] + [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" @@ -13043,7 +13062,6 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", - "pallet-base-fee", "pallet-block-reward", "pallet-chain-extension-assets", "pallet-chain-extension-dapps-staking", @@ -13054,6 +13072,7 @@ dependencies = [ "pallet-contracts-primitives", "pallet-dapps-staking", "pallet-democracy", + "pallet-dynamic-evm-base-fee", "pallet-ethereum", "pallet-ethereum-checked", "pallet-evm", diff --git a/Cargo.toml b/Cargo.toml index d63c7c2e35..c5828cc529 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,6 +278,7 @@ pallet-xc-asset-config = { path = "./pallets/xc-asset-config", default-features pallet-xvm = { path = "./pallets/xvm", default-features = false } pallet-xcm = { path = "./pallets/pallet-xcm", default-features = false } pallet-ethereum-checked = { path = "./pallets/ethereum-checked", default-features = false } +pallet-dynamic-evm-base-fee = { path = "./pallets/dynamic-evm-base-fee", default-features = false } pallet-unified-accounts = { path = "./pallets/unified-accounts", default-features = false } astar-primitives = { path = "./primitives", default-features = false } diff --git a/bin/collator/src/local/chain_spec.rs b/bin/collator/src/local/chain_spec.rs index 8d56c8747c..798370c075 100644 --- a/bin/collator/src/local/chain_spec.rs +++ b/bin/collator/src/local/chain_spec.rs @@ -19,10 +19,10 @@ //! Chain specifications. use local_runtime::{ - wasm_binary_unwrap, AccountId, AuraConfig, AuraId, BalancesConfig, BaseFeeConfig, - BlockRewardConfig, CouncilConfig, DemocracyConfig, EVMConfig, GenesisConfig, GrandpaConfig, - GrandpaId, Precompiles, Signature, SudoConfig, SystemConfig, TechnicalCommitteeConfig, - TreasuryConfig, VestingConfig, + wasm_binary_unwrap, AccountId, AuraConfig, AuraId, BalancesConfig, BlockRewardConfig, + CouncilConfig, DemocracyConfig, EVMConfig, GenesisConfig, GrandpaConfig, GrandpaId, + Precompiles, Signature, SudoConfig, SystemConfig, TechnicalCommitteeConfig, TreasuryConfig, + VestingConfig, }; use sc_service::ChainType; use sp_core::{crypto::Ss58Codec, sr25519, Pair, Public}; @@ -154,10 +154,6 @@ fn testnet_genesis( .collect(), }, ethereum: Default::default(), - base_fee: BaseFeeConfig::new( - sp_core::U256::from(1_000_000_000u64), - sp_runtime::Permill::zero(), - ), sudo: SudoConfig { key: Some(root_key), }, diff --git a/bin/collator/src/parachain/chain_spec/shibuya.rs b/bin/collator/src/parachain/chain_spec/shibuya.rs index 843073ab3b..bdd00127c0 100644 --- a/bin/collator/src/parachain/chain_spec/shibuya.rs +++ b/bin/collator/src/parachain/chain_spec/shibuya.rs @@ -21,11 +21,10 @@ use cumulus_primitives_core::ParaId; use sc_service::ChainType; use shibuya_runtime::{ - wasm_binary_unwrap, AccountId, AuraConfig, AuraId, Balance, BalancesConfig, BaseFeeConfig, - BlockRewardConfig, CollatorSelectionConfig, CouncilConfig, DemocracyConfig, EVMChainIdConfig, - EVMConfig, GenesisConfig, ParachainInfoConfig, Precompiles, SessionConfig, SessionKeys, - Signature, SudoConfig, SystemConfig, TechnicalCommitteeConfig, TreasuryConfig, VestingConfig, - SBY, + wasm_binary_unwrap, AccountId, AuraConfig, AuraId, Balance, BalancesConfig, BlockRewardConfig, + CollatorSelectionConfig, CouncilConfig, DemocracyConfig, EVMChainIdConfig, EVMConfig, + GenesisConfig, ParachainInfoConfig, Precompiles, SessionConfig, SessionKeys, Signature, + SudoConfig, SystemConfig, TechnicalCommitteeConfig, TreasuryConfig, VestingConfig, SBY, }; use sp_core::{sr25519, Pair, Public}; @@ -158,10 +157,6 @@ fn make_genesis( }) .collect(), }, - base_fee: BaseFeeConfig::new( - sp_core::U256::from(1_000_000_000), - sp_runtime::Permill::zero(), - ), evm_chain_id: EVMChainIdConfig { chain_id: 0x51 }, ethereum: Default::default(), polkadot_xcm: Default::default(), diff --git a/pallets/dynamic-evm-base-fee/Cargo.toml b/pallets/dynamic-evm-base-fee/Cargo.toml new file mode 100644 index 0000000000..e9c2177b6d --- /dev/null +++ b/pallets/dynamic-evm-base-fee/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "pallet-dynamic-evm-base-fee" +version = "0.1.0" +license = "GPL-3.0-or-later" +description = "Handler for dynamic EVM base fee for Astar tokenomics v2." +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +# Substrate +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-transaction-payment = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } + +# Frontier +fp-evm = { workspace = true } + +[dev-dependencies] +num-traits = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "num-traits/std", + # Substrate + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-runtime/std", + "pallet-transaction-payment/std", + "pallet-balances/std", + "pallet-timestamp/std", + "frame-benchmarking/std", + # Frontier + "fp-evm/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-transaction-payment/try-runtime", +] diff --git a/pallets/dynamic-evm-base-fee/src/benchmarking.rs b/pallets/dynamic-evm-base-fee/src/benchmarking.rs new file mode 100644 index 0000000000..eb94e5fc67 --- /dev/null +++ b/pallets/dynamic-evm-base-fee/src/benchmarking.rs @@ -0,0 +1,99 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use super::*; + +use fp_evm::FeeCalculator; +use frame_benchmarking::v2::*; +use frame_support::traits::Hooks; +use frame_system::RawOrigin; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn base_fee_per_gas_adjustment() { + let (first_block, second_block) = (T::BlockNumber::from(1u32), T::BlockNumber::from(2u32)); + + // Setup actions, should ensure some value is written to storage. + Pallet::::on_initialize(first_block); + Pallet::::on_finalize(first_block); + assert!( + BaseFeePerGas::::exists(), + "Value should exist in storage after first on_finalize call" + ); + + Pallet::::on_initialize(second_block); + let init_bfpg = BaseFeePerGas::::get(); + + #[block] + { + Pallet::::on_finalize(second_block); + } + + // Ensure that the value has changed. + assert!(BaseFeePerGas::::get() != init_bfpg); + } + + #[benchmark] + fn set_base_fee_per_gas() { + let old_bfpg = BaseFeePerGas::::get(); + let new_bfpg = old_bfpg + 1; + + #[extrinsic_call] + _(RawOrigin::Root, new_bfpg); + + // Ensure that the value has changed. + assert_eq!(BaseFeePerGas::::get(), new_bfpg); + } + + #[benchmark] + fn min_gas_price() { + let first_block = T::BlockNumber::from(1u32); + + // Setup actions, should ensure some value is written to storage. + Pallet::::on_initialize(first_block); + Pallet::::on_finalize(first_block); + assert!( + BaseFeePerGas::::exists(), + "Value should exist in storage after first on_finalize call" + ); + + #[block] + { + let _ = Pallet::::min_gas_price(); + } + } + + impl_benchmark_test_suite!( + Pallet, + crate::benchmarking::tests::new_test_ext(), + crate::mock::TestRuntime, + ); +} + +#[cfg(test)] +mod tests { + use crate::mock; + use frame_support::sp_io::TestExternalities; + + pub fn new_test_ext() -> TestExternalities { + mock::ExtBuilder::build() + } +} diff --git a/pallets/dynamic-evm-base-fee/src/lib.rs b/pallets/dynamic-evm-base-fee/src/lib.rs new file mode 100644 index 0000000000..5574346825 --- /dev/null +++ b/pallets/dynamic-evm-base-fee/src/lib.rs @@ -0,0 +1,238 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Dynamic Evm Base Fee Pallet +//! +//! ## Overview +//! +//! The pallet is responsible for calculating `Base Fee Per Gas` value, according to the current system parameters. +//! This is not like `EIP-1559`, instead it's intended for `Astar` and `Astar-like` networks, which allow both +//! **Substrate native transactions** (which in `Astar` case reuse Polkadot transaction fee approach) +//! and **EVM transactions** (which use `Base Fee Per Gas`). +//! +//! For a more detailed description, reader is advised to refer to Astar Network forum post about [Tokenomics 2.0](https://forum.astar.network/t/astar-tokenomics-2-0-a-dynamically-adjusted-inflation/4924). +//! +//! ## Approach +//! +//! The core formula this pallet tries to satisfy is: +//! +//! base_fee_per_gas = adjustment_factor * weight_factor * 25 / 98974 +//! +//! Where: +//! * **adjustment_factor** - is a value that changes in-between the blocks, related to the block fill ratio. +//! * **weight_factor** - fixed constant, used to convert consumed _weight_ to _fee_. +//! +//! The implementation doesn't make any hard requirements on these values, and only requires that a type implementing `Get<_>` provides them. +//! +//! ## Implementation +//! +//! The core logic is implemented in `on_finalize` hook, which is called at the end of each block. +//! This pallet's hook should be called AFTER whichever pallet's hook is responsible for updating **adjustment factor**. +//! +//! The hook will calculate the ideal new `base_fee_per_gas` value, and then clamp it in between the allowed limits. +//! +//! ## Interface +//! +//! Pallet provides an implementation of `FeeCalculator` trait. This makes it usable directly in `pallet-evm`. +//! +//! A _root-only_ extrinsic is provided to allow setting the `base_fee_per_gas` value manually. +//! +//! ## Practical Remarks +//! +//! According to the proposed **Tokenomics 2.0**, max amount that adjustment factor will be able to change on live networks in-between blocks is: +//! +//! adjustment_new = adjustment_old * (1 + adj + adj^2/2) +//! +//! adj = v * (s - s*) +//! --> recommended _v_ value: 0.000_015 +//! --> largest 's' delta: (1 - 0.25) = **0.75** +//! +//! (for variable explanation please check the linked forum post above) +//! (in short: `v` - variability factor, `s` - current block fill ratio, `s*` - ideal block fill ratio) +//! +//! adj = 0.000015 * (1 - 0.25) = **0.000_011_25** +//! (1 + 0.000_011_25 + 0.000_011_25^2/2) = (1 + 0.000_011_25 + 0.000_000_000_063_281) = **1,000_011_250_063_281** +//! +//! Discarding the **1**, and only considering the decimals, this can be expressed as ratio: +//! Expressed as ratio: 11_250_063_281 / 1_000_000_000_000_000. +//! This is a much smaller change compared to the max step limit ratio we'll use to limit bfpg alignment. +//! This means that once equilibrium is reached (fees are aligned), the `StepLimitRatio` will be larger than the max possible adjustment, essentially eliminating its effect. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::weights::Weight; +use sp_core::U256; +use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128, Perquintill}; + +pub use self::pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + use super::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type + type RuntimeEvent: From + IsType<::RuntimeEvent>; + /// Default base fee per gas value. Used in genesis if no other value specified explicitly. + type DefaultBaseFeePerGas: Get; + /// Minimum value 'base fee per gas' can be adjusted to. This is a defensive measure to prevent the fee from being too low. + type MinBaseFeePerGas: Get; + /// Maximum value 'base fee per gas' can be adjusted to. This is a defensive measure to prevent the fee from being too high. + type MaxBaseFeePerGas: Get; + /// Getter for the fee adjustment factor used in 'base fee per gas' formula. This is expected to change in-between the blocks (doesn't have to though). + type AdjustmentFactor: Get; + /// The so-called `weight_factor` in the 'base fee per gas' formula. + type WeightFactor: Get; + /// Ratio limit on how much the 'base fee per gas' can change in-between two blocks. + /// It's expressed as percentage, and used to calculate the delta between the old and new value. + /// E.g. if the current 'base fee per gas' is 100, and the limit is 10%, then the new base fee per gas can be between 90 and 110. + type StepLimitRatio: Get; + /// Weight information for extrinsics & functions of this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::type_value] + pub fn DefaultBaseFeePerGas() -> U256 { + T::DefaultBaseFeePerGas::get() + } + + #[pallet::storage] + pub type BaseFeePerGas = StorageValue<_, U256, ValueQuery, DefaultBaseFeePerGas>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// New `base fee per gas` value has been force-set. + NewBaseFeePerGas { fee: U256 }, + } + + #[pallet::error] + pub enum Error { + /// Specified value is outside of the allowed range. + ValueOutOfBounds, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_: T::BlockNumber) -> Weight { + T::WeightInfo::base_fee_per_gas_adjustment() + } + + fn on_finalize(_n: ::BlockNumber) { + BaseFeePerGas::::mutate(|base_fee_per_gas| { + let old_bfpg = *base_fee_per_gas; + + // Maximum step we're allowed to move the base fee per gas by. + let max_step = { + let old_bfpg_u128: u128 = old_bfpg.unique_saturated_into(); + let step = T::StepLimitRatio::get() * old_bfpg_u128; + U256::from(step) + }; + + // It's possible current base fee per gas is outside of the allowed range. + // This can & will happen when this solution is deployed on live networks. + // + // In such scenario, we will discard the lower & upper bounds configured in the runtime. + // Once these bounds are reached ONCE, the runtime logic will prevent them from going out of bounds again. + let apply_configured_bounds = old_bfpg >= T::MinBaseFeePerGas::get() + && old_bfpg <= T::MaxBaseFeePerGas::get(); + let (lower_limit, upper_limit) = if apply_configured_bounds { + ( + T::MinBaseFeePerGas::get().max(old_bfpg.saturating_sub(max_step)), + T::MaxBaseFeePerGas::get().min(old_bfpg.saturating_add(max_step)), + ) + } else { + ( + old_bfpg.saturating_sub(max_step), + old_bfpg.saturating_add(max_step), + ) + }; + + // Calculate ideal new 'base_fee_per_gas' according to the formula + let ideal_new_bfpg = T::AdjustmentFactor::get() + // Weight factor should be multiplied first since it's a larger number, to avoid precision loss. + .saturating_mul_int(T::WeightFactor::get()) + .saturating_mul(25) + .saturating_div(98974); + + // Clamp the ideal value in between the allowed limits + *base_fee_per_gas = U256::from(ideal_new_bfpg).clamp(lower_limit, upper_limit); + }) + } + + fn integrity_test() { + assert!(T::MinBaseFeePerGas::get() <= T::MaxBaseFeePerGas::get(), + "Minimum base fee per gas has to be equal or lower than maximum allowed base fee per gas."); + + assert!(T::DefaultBaseFeePerGas::get() >= T::MinBaseFeePerGas::get(), + "Default base fee per gas has to be equal or higher than minimum allowed base fee per gas."); + assert!(T::DefaultBaseFeePerGas::get() <= T::MaxBaseFeePerGas::get(), + "Default base fee per gas has to be equal or lower than maximum allowed base fee per gas."); + + assert!(T::MaxBaseFeePerGas::get() <= U256::from(u128::MAX), + "Maximum base fee per gas has to be equal or lower than u128::MAX, otherwise precision loss will occur."); + } + } + + #[pallet::call] + impl Pallet { + /// `root-only` extrinsic to set the `base_fee_per_gas` value manually. + /// The specified value has to respect min & max limits configured in the runtime. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_base_fee_per_gas())] + pub fn set_base_fee_per_gas(origin: OriginFor, fee: U256) -> DispatchResult { + ensure_root(origin)?; + ensure!( + fee >= T::MinBaseFeePerGas::get() && fee <= T::MaxBaseFeePerGas::get(), + Error::::ValueOutOfBounds + ); + + BaseFeePerGas::::put(fee); + Self::deposit_event(Event::NewBaseFeePerGas { fee }); + Ok(()) + } + } +} + +impl fp_evm::FeeCalculator for Pallet { + fn min_gas_price() -> (U256, Weight) { + (BaseFeePerGas::::get(), T::WeightInfo::min_gas_price()) + } +} diff --git a/pallets/dynamic-evm-base-fee/src/mock.rs b/pallets/dynamic-evm-base-fee/src/mock.rs new file mode 100644 index 0000000000..3d7db1e4cb --- /dev/null +++ b/pallets/dynamic-evm-base-fee/src/mock.rs @@ -0,0 +1,182 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#![cfg(test)] + +use super::*; +use crate as pallet_dynamic_evm_base_fee; + +use frame_support::{ + construct_runtime, parameter_types, + sp_io::TestExternalities, + storage, + traits::{ConstU128, ConstU32, ConstU64, Get}, + weights::constants::RocksDbWeight, +}; +use parity_scale_codec::Encode; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup, One}, + FixedU128, Perquintill, +}; + +pub(crate) type AccountId = u128; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u128; + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = RocksDbWeight; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for TestRuntime { + type MaxLocks = ConstU32<4>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<2>; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +impl pallet_timestamp::Config for TestRuntime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +parameter_types! { + pub DefaultBaseFeePerGas: U256 = U256::from(1_500_000_000_000_u128); + pub MinBaseFeePerGas: U256 = U256::from(800_000_000_000_u128); + pub MaxBaseFeePerGas: U256 = U256::from(80_000_000_000_000_u128); + pub StepLimitRation: Perquintill = Perquintill::from_rational(30_u128, 1_000_000); +} + +impl pallet_dynamic_evm_base_fee::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type DefaultBaseFeePerGas = DefaultBaseFeePerGas; + type MinBaseFeePerGas = MinBaseFeePerGas; + type MaxBaseFeePerGas = MaxBaseFeePerGas; + type AdjustmentFactor = GetAdjustmentFactor; + type WeightFactor = ConstU128<30_000_000_000_000_000>; + type StepLimitRatio = StepLimitRation; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub struct TestRuntime + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + DynamicEvmBaseFee: pallet_dynamic_evm_base_fee, + } +); + +const ADJUSTMENT_FACTOR: &[u8] = b":adj_factor_evm"; + +/// Helper method to set the adjustment factor used by the pallet. +pub fn set_adjustment_factor(factor: FixedU128) { + storage::unhashed::put_raw(&ADJUSTMENT_FACTOR, &factor.encode()); +} + +pub struct GetAdjustmentFactor; +impl Get for GetAdjustmentFactor { + fn get() -> FixedU128 { + storage::unhashed::get::(&ADJUSTMENT_FACTOR).unwrap_or_default() + } +} + +pub struct ExtBuilder; +impl ExtBuilder { + pub fn build() -> TestExternalities { + let storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| { + set_adjustment_factor(FixedU128::one()); + System::set_block_number(1); + }); + ext + } +} + +/// Ideal `base fee per gas` value according to the fee alignment formula. +/// It changes dynamically based on `adjustment factor` and `weight factor` parameters. +pub fn get_ideal_bfpg() -> U256 { + U256::from( + ::AdjustmentFactor::get() + .saturating_mul_int::( + ::WeightFactor::get(), + ) + .saturating_mul(25) + .saturating_div(98974), + ) +} + +/// Max step limit describes how much `base fee per gas` can move in any direction during one block. +pub fn get_max_step_limit() -> U256 { + let bfpg: u128 = BaseFeePerGas::::get().unique_saturated_into(); + let max_allowed_step: u128 = ::StepLimitRatio::get() * bfpg; + + U256::from(max_allowed_step) +} diff --git a/pallets/dynamic-evm-base-fee/src/tests.rs b/pallets/dynamic-evm-base-fee/src/tests.rs new file mode 100644 index 0000000000..dc65f31fe6 --- /dev/null +++ b/pallets/dynamic-evm-base-fee/src/tests.rs @@ -0,0 +1,325 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#![cfg(test)] + +use super::*; +use mock::*; + +use frame_support::{ + assert_noop, assert_ok, + traits::{Get, OnFinalize}, +}; +use num_traits::Bounded; +use sp_runtime::{ + traits::{BadOrigin, One, Zero}, + FixedU128, +}; + +use fp_evm::FeeCalculator; + +#[test] +fn default_base_fee_per_gas_works() { + ExtBuilder::build().execute_with(|| { + // Genesis state check + assert_eq!( + BaseFeePerGas::::get(), + ::DefaultBaseFeePerGas::get(), + "Init bfpg should be equal to the specified default one." + ) + }); +} + +#[test] +fn set_base_fee_per_gas_works() { + ExtBuilder::build().execute_with(|| { + // sanity check + assert_eq!( + BaseFeePerGas::::get(), + ::DefaultBaseFeePerGas::get() + ); + + // Ensure we can change the bfpg value via root + for new_base_fee_per_gas in [ + ::MinBaseFeePerGas::get(), + ::MaxBaseFeePerGas::get(), + ] { + assert_ok!(DynamicEvmBaseFee::set_base_fee_per_gas( + RuntimeOrigin::root(), + new_base_fee_per_gas + )); + System::assert_last_event(mock::RuntimeEvent::DynamicEvmBaseFee( + Event::NewBaseFeePerGas { + fee: new_base_fee_per_gas, + }, + )); + assert_eq!(BaseFeePerGas::::get(), new_base_fee_per_gas); + } + }); +} + +#[test] +fn set_base_fee_per_gas_value_out_of_bounds_fails() { + ExtBuilder::build().execute_with(|| { + // Out of bound values + let too_small_base_fee_per_gas = + ::MinBaseFeePerGas::get() - 1; + let too_big_base_fee_per_gas = ::MaxBaseFeePerGas::get() + 1; + + assert_noop!( + DynamicEvmBaseFee::set_base_fee_per_gas( + RuntimeOrigin::root(), + too_small_base_fee_per_gas + ), + Error::::ValueOutOfBounds + ); + assert_noop!( + DynamicEvmBaseFee::set_base_fee_per_gas( + RuntimeOrigin::root(), + too_big_base_fee_per_gas + ), + Error::::ValueOutOfBounds + ); + }); +} + +#[test] +fn set_base_fee_per_gas_non_root_fails() { + ExtBuilder::build().execute_with(|| { + assert_noop!( + DynamicEvmBaseFee::set_base_fee_per_gas( + RuntimeOrigin::signed(1), + ::MinBaseFeePerGas::get() + ), + BadOrigin + ); + }); +} + +#[test] +fn min_gas_price_works() { + ExtBuilder::build().execute_with(|| { + let new_base_fee_per_gas = + ::MinBaseFeePerGas::get() + 19 * 17; + assert_ok!(DynamicEvmBaseFee::set_base_fee_per_gas( + RuntimeOrigin::root(), + new_base_fee_per_gas + )); + + let expected_weight: Weight = + <::WeightInfo as weights::WeightInfo>::min_gas_price(); + assert_eq!( + DynamicEvmBaseFee::min_gas_price(), + (new_base_fee_per_gas, expected_weight) + ); + }); +} + +#[test] +fn unit_adjustment_factor_no_change() { + ExtBuilder::build().execute_with(|| { + // Prep init values - ideal bfpg, and unit adjustment factor + let init_bfpg = get_ideal_bfpg(); + BaseFeePerGas::::set(init_bfpg); + set_adjustment_factor(FixedU128::one()); + + DynamicEvmBaseFee::on_finalize(1); + assert_eq!( + BaseFeePerGas::::get(), + init_bfpg, + "bfpg should remain the same" + ); + }); +} + +#[test] +fn bfpg_bounds_are_respected() { + ExtBuilder::build().execute_with(|| { + // Lower bound + let min_bfpg = ::MinBaseFeePerGas::get(); + BaseFeePerGas::::set(min_bfpg); + + // This should bring the ideal bfpg value to zero + set_adjustment_factor(FixedU128::zero()); + assert!(get_ideal_bfpg().is_zero(), "Sanity check"); + + DynamicEvmBaseFee::on_finalize(1); + assert_eq!( + BaseFeePerGas::::get(), + min_bfpg, + "bfpg must not go below lower threshold." + ); + + // Upper limit + let upper_bfpg = ::MaxBaseFeePerGas::get(); + BaseFeePerGas::::set(upper_bfpg); + + // This should bring the ideal bfpg very high, well above max value + set_adjustment_factor(FixedU128::max_value()); + assert!(get_ideal_bfpg() > upper_bfpg, "Sanity check"); + + DynamicEvmBaseFee::on_finalize(2); + assert_eq!( + BaseFeePerGas::::get(), + upper_bfpg, + "bfpg must not go above threshold" + ); + }); +} + +#[test] +fn step_limit_ratio_is_respected() { + ExtBuilder::build().execute_with(|| { + // Lower bound, high adjustment factor + let min_bfpg = ::MinBaseFeePerGas::get(); + BaseFeePerGas::::set(min_bfpg); + set_adjustment_factor(FixedU128::max_value()); + let step_limit = get_max_step_limit(); + + DynamicEvmBaseFee::on_finalize(1); + assert_eq!( + BaseFeePerGas::::get(), + min_bfpg + step_limit, + "Step limit ratio in ascending direction was not respected." + ); + + // Upper bound, low adjustment factor + let max_bfpg = ::MaxBaseFeePerGas::get(); + BaseFeePerGas::::set(max_bfpg); + set_adjustment_factor(FixedU128::zero()); + let step_limit = get_max_step_limit(); + + DynamicEvmBaseFee::on_finalize(2); + assert_eq!( + BaseFeePerGas::::get(), + max_bfpg - step_limit, + "Step limit ratio in descending direction was not respected." + ); + }); +} + +#[test] +fn bfpg_full_spectrum_change_works() { + ExtBuilder::build().execute_with(|| { + // Set bfpg to lowest possible, and adjustment factor to highest possible + let min_bfpg = ::MinBaseFeePerGas::get(); + BaseFeePerGas::::set(min_bfpg); + set_adjustment_factor(FixedU128::max_value()); + + // Run for limited amount of iterations until upper bound is reached + let target_bfpg = ::MaxBaseFeePerGas::get(); + let mut counter = 1; + let iter_limit = 500_000; // safety limit to avoid endless loop + while counter <= iter_limit && BaseFeePerGas::::get() < target_bfpg { + DynamicEvmBaseFee::on_finalize(counter); + counter += 1; + } + + assert_eq!(BaseFeePerGas::::get(), target_bfpg, + "bfpg upper bound not reached - either it's not enough iterations or some precision loss occurs."); + }); +} + +#[test] +fn bfpg_matches_expected_value_for_so_called_average_transaction() { + ExtBuilder::build().execute_with(|| { + // The new proposed models suggests to use the following formula to calculate the base fee per gas: + // + // bfpg = (adj_factor * weight_factor * 25_000) / 9_897_4000 + let init_bfpg = get_ideal_bfpg(); + BaseFeePerGas::::set(init_bfpg); + let init_adj_factor = ::AdjustmentFactor::get(); + + // Slighly increase the adjustment factor, and calculate the new base fee per gas + // + // To keep it closer to reality, let's assume we're using the proposed variability factor of 0.000_015. + // Let's also assume that block fullness difference is 0.01 (1%). + // This should result in the adjustment factor of 0.000_001_5. + // + // NOTE: it's important to keep the increase small so that the step doesn't saturate + let change = FixedU128::from_rational(1500, 1_000_000_000); + let new_adj_factor = init_adj_factor + change; + assert!(new_adj_factor > init_adj_factor, "Sanity check"); + set_adjustment_factor(new_adj_factor); + + // Calculate the new expected base fee per gas + let weight_factor: u128 = ::WeightFactor::get(); + let expected_bfpg = + U256::from(new_adj_factor.saturating_mul_int(weight_factor) * 25_000 / 9_897_4000); + + // Calculate the new base fee per gas in the pallet + DynamicEvmBaseFee::on_finalize(1); + + // Assert calculated value is as expected + let new_bfpg = BaseFeePerGas::::get(); + assert!(new_bfpg > init_bfpg, "Sanity check"); + assert_eq!(new_bfpg, expected_bfpg); + + // Also check the opposite direction + let new_adj_factor = init_adj_factor - change; + set_adjustment_factor(new_adj_factor); + let expected_bfpg = + U256::from(new_adj_factor.saturating_mul_int(weight_factor) * 25_000 / 9_897_4000); + + // Calculate the new base fee per gas in the pallet + DynamicEvmBaseFee::on_finalize(2); + // Assert calculated value is as expected + let new_bfpg = BaseFeePerGas::::get(); + assert!(new_bfpg < init_bfpg, "Sanity check"); + assert_eq!(new_bfpg, expected_bfpg); + }); +} + +#[test] +fn lower_upper_bounds_ignored_if_bfpg_is_outside() { + ExtBuilder::build().execute_with(|| { + // Set the initial bfpg to be outside of the allowed range. + // It's important reduction is sufficient so we're still below the minimum limit after the adjustment. + let delta = 100_000_000; + + // First test when bfpg is too little + let too_small_bfpg = ::MinBaseFeePerGas::get() - delta; + BaseFeePerGas::::set(too_small_bfpg); + DynamicEvmBaseFee::on_finalize(1); + + assert!( + BaseFeePerGas::::get() > too_small_bfpg, + "Bfpg should have increased slightly." + ); + assert!( + BaseFeePerGas::::get() + < ::MinBaseFeePerGas::get(), + "For this test, bfpg should still be below the minimum limit." + ); + + // Repeat the same test but this time bfpg is too big + let too_big_bfpg = ::MaxBaseFeePerGas::get() + delta; + BaseFeePerGas::::set(too_big_bfpg); + DynamicEvmBaseFee::on_finalize(2); + + assert!( + BaseFeePerGas::::get() < too_big_bfpg, + "Bfpg should have decreased slightly." + ); + assert!( + BaseFeePerGas::::get() + < ::MaxBaseFeePerGas::get(), + "For this test, bfpg should still be above the maximum limit." + ); + }); +} diff --git a/pallets/dynamic-evm-base-fee/src/weights.rs b/pallets/dynamic-evm-base-fee/src/weights.rs new file mode 100644 index 0000000000..46c67f3946 --- /dev/null +++ b/pallets/dynamic-evm-base-fee/src/weights.rs @@ -0,0 +1,130 @@ + +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_dynamic_evm_base_fee +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("shibuya-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=shibuya-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_dynamic_evm_base_fee +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./benchmark-results/dynamic_evm_base_fee_weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_dynamic_evm_base_fee. +pub trait WeightInfo { + fn base_fee_per_gas_adjustment() -> Weight; + fn set_base_fee_per_gas() -> Weight; + fn min_gas_price() -> Weight; +} + +/// Weights for pallet_dynamic_evm_base_fee using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:1 w:1) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + fn base_fee_per_gas_adjustment() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 8_560_000 picoseconds. + Weight::from_parts(8_778_000, 1517) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:0 w:1) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn set_base_fee_per_gas() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_883_000 picoseconds. + Weight::from_parts(8_060_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:1 w:0) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn min_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `98` + // Estimated: `1517` + // Minimum execution time: 4_201_000 picoseconds. + Weight::from_parts(4_399_000, 1517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:1 w:1) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + fn base_fee_per_gas_adjustment() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 8_560_000 picoseconds. + Weight::from_parts(8_778_000, 1517) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:0 w:1) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn set_base_fee_per_gas() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_883_000 picoseconds. + Weight::from_parts(8_060_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: DynamicEvmBaseFee BaseFeePerGas (r:1 w:0) + /// Proof: DynamicEvmBaseFee BaseFeePerGas (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn min_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `98` + // Estimated: `1517` + // Minimum execution time: 4_201_000 picoseconds. + Weight::from_parts(4_399_000, 1517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } +} diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 82bddf6272..fd32fdf941 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true log = { workspace = true, optional = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +smallvec = { workspace = true } fp-rpc = { workspace = true } fp-self-contained = { workspace = true } @@ -20,7 +21,6 @@ frame-system = { workspace = true } pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-balances = { workspace = true } -pallet-base-fee = { workspace = true } pallet-collective = { workspace = true } pallet-contracts = { workspace = true } pallet-contracts-primitives = { workspace = true } @@ -70,6 +70,7 @@ pallet-block-reward = { workspace = true } pallet-chain-extension-dapps-staking = { workspace = true } pallet-chain-extension-xvm = { workspace = true } pallet-dapps-staking = { workspace = true } +pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } @@ -116,7 +117,7 @@ std = [ "pallet-chain-extension-dapps-staking/std", "pallet-chain-extension-xvm/std", "pallet-dapps-staking/std", - "pallet-base-fee/std", + "pallet-dynamic-evm-base-fee/std", "pallet-ethereum/std", "pallet-evm/std", "pallet-evm-precompile-blake2/std", @@ -186,6 +187,7 @@ runtime-benchmarks = [ "pallet-unified-accounts/runtime-benchmarks", "astar-primitives/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-dynamic-evm-base-fee/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", @@ -216,7 +218,7 @@ try-runtime = [ "pallet-proxy/try-runtime", "pallet-treasury/try-runtime", "pallet-preimage/try-runtime", - "pallet-base-fee/try-runtime", + "pallet-dynamic-evm-base-fee/try-runtime", "pallet-evm/try-runtime", "pallet-ethereum-checked/try-runtime", ] diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index be79454ca0..b372b6bdbf 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -31,8 +31,9 @@ use frame_support::{ EqualPrivilegeOnly, FindAuthor, Get, InstanceFilter, Nothing, OnFinalize, WithdrawReasons, }, weights::{ - constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, - ConstantMultiplier, IdentityFee, Weight, + constants::{ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, + ConstantMultiplier, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, + WeightToFeePolynomial, }, ConsensusEngineId, PalletId, }; @@ -44,6 +45,7 @@ use pallet_ethereum::PostLogContent; use pallet_evm::{FeeCalculator, GasWeightMapping, Runner}; use pallet_evm_precompile_assets_erc20::AddressToAssetId; use pallet_grandpa::{fg_primitives, AuthorityList as GrandpaAuthorityList}; +use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use parity_scale_codec::{Compact, Decode, Encode, MaxEncodedLen}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, ConstBool, OpaqueMetadata, H160, H256, U256}; @@ -54,7 +56,7 @@ use sp_runtime::{ DispatchInfoOf, Dispatchable, NumberFor, PostDispatchInfoOf, UniqueSaturatedInto, }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, - ApplyExtrinsicResult, RuntimeDebug, + ApplyExtrinsicResult, FixedPointNumber, Perbill, Permill, Perquintill, RuntimeDebug, }; use sp_std::prelude::*; @@ -72,12 +74,9 @@ pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_grandpa::AuthorityId as GrandpaId; pub use pallet_timestamp::Call as TimestampCall; -use pallet_transaction_payment::CurrencyAdapter; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -pub use sp_runtime::{Perbill, Permill}; - #[cfg(feature = "std")] /// Wasm binary unwrapped. If built with `BUILD_DUMMY_WASM_BINARY`, the function panics. pub fn wasm_binary_unwrap() -> &'static [u8] { @@ -331,18 +330,81 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = astar_primitives::benchmarks::AssetsBenchmarkHelper; } +// These values are based on the Astar 2.0 Tokenomics Modeling report. parameter_types! { - pub const TransactionByteFee: Balance = 1; + pub const TransactionLengthFeeFactor: Balance = 23_500_000_000_000; // 0.000_023_500_000_000_000 SBY per byte + pub const WeightFeeFactor: Balance = 30_855_000_000_000_000; // Around 0.03 SBY per unit of ref time. + pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); pub const OperationalFeeMultiplier: u8 = 5; + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 666_667); // 0.000_015 + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 10); // 0.1 + pub MaximumMultiplier: Multiplier = Multiplier::saturating_from_integer(10); // 10 +} + +/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the +/// node's balance type. +/// +/// This should typically create a mapping between the following ranges: +/// - [0, MAXIMUM_BLOCK_WEIGHT] +/// - [Balance::min, Balance::max] +/// +/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: +/// - Setting it to `0` will essentially disable the weight fee. +/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + let p = WeightFeeFactor::get(); + let q = Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec::smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } } impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; - type WeightToFee = IdentityFee; + type WeightToFee = WeightToFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; - type FeeMultiplierUpdate = (); - type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = TargetedFeeAdjustment< + Self, + TargetBlockFullness, + AdjustmentVariable, + MinimumMultiplier, + MaximumMultiplier, + >; + type LengthToFee = ConstantMultiplier; +} + +parameter_types! { + pub DefaultBaseFeePerGas: U256 = U256::from(1_470_000_000_000_u128); + pub MinBaseFeePerGas: U256 = U256::from(800_000_000_000_u128); + pub MaxBaseFeePerGas: U256 = U256::from(80_000_000_000_000_u128); + pub StepLimitRatio: Perquintill = Perquintill::from_rational(5_u128, 100_000); +} + +/// Simple wrapper for fetching current native transaction fee weight fee multiplier. +pub struct AdjustmentFactorGetter; +impl Get for AdjustmentFactorGetter { + fn get() -> Multiplier { + pallet_transaction_payment::NextFeeMultiplier::::get() + } +} + +impl pallet_dynamic_evm_base_fee::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type DefaultBaseFeePerGas = DefaultBaseFeePerGas; + type MinBaseFeePerGas = MinBaseFeePerGas; + type MaxBaseFeePerGas = MaxBaseFeePerGas; + type AdjustmentFactor = AdjustmentFactorGetter; + type WeightFactor = WeightFeeFactor; + type StepLimitRatio = StepLimitRatio; + type WeightInfo = pallet_dynamic_evm_base_fee::weights::SubstrateWeight; } parameter_types! { @@ -477,33 +539,6 @@ impl pallet_xvm::Config for Runtime { type WeightInfo = pallet_xvm::weights::SubstrateWeight; } -parameter_types! { - // Tells `pallet_base_fee` whether to calculate a new BaseFee `on_finalize` or not. - pub DefaultBaseFeePerGas: U256 = (MILLIAST / 1_000_000).into(); - // At the moment, we don't use dynamic fee calculation for local chain by default - pub DefaultElasticity: Permill = Permill::zero(); -} - -pub struct BaseFeeThreshold; -impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { - fn lower() -> Permill { - Permill::zero() - } - fn ideal() -> Permill { - Permill::from_parts(500_000) - } - fn upper() -> Permill { - Permill::from_parts(1_000_000) - } -} - -impl pallet_base_fee::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Threshold = BaseFeeThreshold; - type DefaultBaseFeePerGas = DefaultBaseFeePerGas; - type DefaultElasticity = DefaultElasticity; -} - /// Current approximation of the gas/s consumption considering /// EVM execution over compiled WASM (on 4.4Ghz CPU). /// Given the 500ms Weight, from which 75% only are used for transactions, @@ -552,7 +587,7 @@ parameter_types! { } impl pallet_evm::Config for Runtime { - type FeeCalculator = BaseFee; + type FeeCalculator = DynamicEvmBaseFee; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; @@ -891,7 +926,7 @@ impl InstanceFilter for ProxyType { | RuntimeCall::DappsStaking(..) // Skip entire EVM pallet // Skip entire Ethereum pallet - | RuntimeCall::BaseFee(..) + | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet | RuntimeCall::Democracy(..) | RuntimeCall::Council(..) @@ -989,7 +1024,7 @@ construct_runtime!( TransactionPayment: pallet_transaction_payment, EVM: pallet_evm, Ethereum: pallet_ethereum, - BaseFee: pallet_base_fee, + DynamicEvmBaseFee: pallet_dynamic_evm_base_fee, Contracts: pallet_contracts, Sudo: pallet_sudo, Assets: pallet_assets, @@ -1117,6 +1152,7 @@ mod benches { [pallet_dapps_staking, DappsStaking] [pallet_block_reward, BlockReward] [pallet_ethereum_checked, EthereumChecked] + [pallet_dynamic_evm_base_fee, DynamicEvmBaseFee] ); } @@ -1509,7 +1545,7 @@ impl_runtime_apis! { } fn elasticity() -> Option { - Some(pallet_base_fee::Elasticity::::get()) + Some(Permill::zero()) } fn gas_limit_multiplier_support() {} diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index eecc607ce7..dae784a038 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -41,7 +41,6 @@ pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-base-fee = { workspace = true } pallet-collective = { workspace = true } pallet-contracts = { workspace = true } pallet-contracts-primitives = { workspace = true } @@ -101,6 +100,7 @@ pallet-chain-extension-dapps-staking = { workspace = true } pallet-chain-extension-xvm = { workspace = true } pallet-collator-selection = { workspace = true } pallet-dapps-staking = { workspace = true } +pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-batch = { workspace = true } @@ -166,7 +166,7 @@ std = [ "pallet-contracts-primitives/std", "pallet-chain-extension-dapps-staking/std", "pallet-chain-extension-xvm/std", - "pallet-base-fee/std", + "pallet-dynamic-evm-base-fee/std", "pallet-ethereum/std", "pallet-preimage/std", "pallet-evm/std", @@ -259,6 +259,7 @@ runtime-benchmarks = [ "orml-xtokens/runtime-benchmarks", "astar-primitives/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-dynamic-evm-base-fee/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", @@ -301,7 +302,7 @@ try-runtime = [ "parachain-info/try-runtime", "pallet-xvm/try-runtime", "pallet-preimage/try-runtime", - "pallet-base-fee/try-runtime", + "pallet-dynamic-evm-base-fee/try-runtime", "pallet-evm/try-runtime", "pallet-unified-accounts/try-runtime", "pallet-ethereum-checked/try-runtime", diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index be682ea427..a3617a9ac5 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -58,7 +58,7 @@ use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ - AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, + AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, DispatchInfoOf, Dispatchable, OpaqueKeys, PostDispatchInfoOf, UniqueSaturatedInto, }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, @@ -104,7 +104,7 @@ pub const MICROSBY: Balance = 1_000_000_000_000; pub const MILLISBY: Balance = 1_000 * MICROSBY; pub const SBY: Balance = 1_000 * MILLISBY; -pub const STORAGE_BYTE_FEE: Balance = 100 * MICROSBY; +pub const STORAGE_BYTE_FEE: Balance = MICROSBY; /// Charge fee for stored bytes and items. pub const fn deposit(items: u32, bytes: u32) -> Balance { @@ -115,10 +115,8 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { /// /// The slight difference to general `deposit` function is because there is fixed bound on how large the DB /// key can grow so it doesn't make sense to have as high deposit per item as in the general approach. -/// -/// TODO: using this requires storage migration (good to test on Shibuya first!) pub const fn contracts_deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 4 * MILLISBY + (bytes as Balance) * STORAGE_BYTE_FEE + items as Balance * 40 * MICROSBY + (bytes as Balance) * STORAGE_BYTE_FEE } /// Change this to adjust the block time. @@ -639,10 +637,9 @@ impl pallet_vesting::Config for Runtime { const MAX_VESTING_SCHEDULES: u32 = 28; } -// TODO: changing depost per item and per byte to `deposit` function will require storage migration it seems parameter_types! { - pub const DepositPerItem: Balance = MILLISBY / 1_000_000; - pub const DepositPerByte: Balance = MILLISBY / 1_000_000; + pub const DepositPerItem: Balance = contracts_deposit(1, 0); + pub const DepositPerByte: Balance = contracts_deposit(0, 1); // Fallback value if storage deposit limit not set by the user pub const DefaultDepositLimit: Balance = contracts_deposit(16, 16 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); @@ -680,13 +677,15 @@ impl pallet_contracts::Config for Runtime { type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } +// These values are based on the Astar 2.0 Tokenomics Modeling report. parameter_types! { - pub const TransactionByteFee: Balance = MILLISBY / 100; + pub const TransactionLengthFeeFactor: Balance = 23_500_000_000_000; // 0.000_023_500_000_000_000 SBY per byte + pub const WeightFeeFactor: Balance = 30_855_000_000_000_000; // Around 0.03 SBY per unit of ref time. pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); pub const OperationalFeeMultiplier: u8 = 5; - pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000); - pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128); - pub MaximumMultiplier: Multiplier = Bounded::max_value(); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 666_667); // 0.000_015 + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 10); // 0.1 + pub MaximumMultiplier: Multiplier = Multiplier::saturating_from_integer(10); // 10 } /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the @@ -703,9 +702,8 @@ pub struct WeightToFee; impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; fn polynomial() -> WeightToFeeCoefficients { - // in Shibuya, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 mSBY: - let p = MILLISBY; - let q = 10 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + let p = WeightFeeFactor::get(); + let q = Balance::from(ExtrinsicBaseWeight::get().ref_time()); smallvec::smallvec![WeightToFeeCoefficient { degree: 1, negative: false, @@ -718,11 +716,12 @@ impl WeightToFeePolynomial for WeightToFee { pub struct DealWithFees; impl OnUnbalanced for DealWithFees { fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(mut fees) = fees_then_tips.next() { + if let Some(fees) = fees_then_tips.next() { + // Burn 80% of fees, rest goes to collators, including 100% of the tips. + let (to_burn, mut collators) = fees.ration(80, 20); if let Some(tips) = fees_then_tips.next() { - tips.merge_into(&mut fees); + tips.merge_into(&mut collators); } - let (to_burn, collators) = fees.ration(20, 80); // burn part of fees drop(to_burn); @@ -745,7 +744,33 @@ impl pallet_transaction_payment::Config for Runtime { MinimumMultiplier, MaximumMultiplier, >; - type LengthToFee = ConstantMultiplier; + type LengthToFee = ConstantMultiplier; +} + +parameter_types! { + pub DefaultBaseFeePerGas: U256 = U256::from(1_470_000_000_000_u128); + pub MinBaseFeePerGas: U256 = U256::from(800_000_000_000_u128); + pub MaxBaseFeePerGas: U256 = U256::from(80_000_000_000_000_u128); + pub StepLimitRatio: Perquintill = Perquintill::from_rational(5_u128, 100_000); +} + +/// Simple wrapper for fetching current native transaction fee weight fee multiplier. +pub struct AdjustmentFactorGetter; +impl Get for AdjustmentFactorGetter { + fn get() -> Multiplier { + pallet_transaction_payment::NextFeeMultiplier::::get() + } +} + +impl pallet_dynamic_evm_base_fee::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type DefaultBaseFeePerGas = DefaultBaseFeePerGas; + type MinBaseFeePerGas = MinBaseFeePerGas; + type MaxBaseFeePerGas = MaxBaseFeePerGas; + type AdjustmentFactor = AdjustmentFactorGetter; + type WeightFactor = WeightFeeFactor; + type StepLimitRatio = StepLimitRatio; + type WeightInfo = pallet_dynamic_evm_base_fee::weights::SubstrateWeight; } parameter_types! { @@ -770,33 +795,6 @@ impl pallet_xvm::Config for Runtime { type WeightInfo = pallet_xvm::weights::SubstrateWeight; } -parameter_types! { - // Tells `pallet_base_fee` whether to calculate a new BaseFee `on_finalize` or not. - pub DefaultBaseFeePerGas: U256 = (MILLISBY / 1_000_000).into(); - // At the moment, we don't use dynamic fee calculation for Shibuya by default - pub DefaultElasticity: Permill = Permill::zero(); -} - -pub struct BaseFeeThreshold; -impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { - fn lower() -> Permill { - Permill::zero() - } - fn ideal() -> Permill { - Permill::from_parts(500_000) - } - fn upper() -> Permill { - Permill::from_parts(1_000_000) - } -} - -impl pallet_base_fee::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Threshold = BaseFeeThreshold; - type DefaultBaseFeePerGas = DefaultBaseFeePerGas; - type DefaultElasticity = DefaultElasticity; -} - /// Current approximation of the gas/s consumption considering /// EVM execution over compiled WASM (on 4.4Ghz CPU). /// Given the 500ms Weight, from which 75% only are used for transactions, @@ -839,7 +837,7 @@ parameter_types! { } impl pallet_evm::Config for Runtime { - type FeeCalculator = BaseFee; + type FeeCalculator = DynamicEvmBaseFee; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; @@ -1114,7 +1112,7 @@ impl InstanceFilter for ProxyType { | RuntimeCall::XcAssetConfig(..) // Skip entire EVM pallet // Skip entire Ethereum pallet - | RuntimeCall::BaseFee(..) + | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet | RuntimeCall::Democracy(..) | RuntimeCall::Council(..) @@ -1254,7 +1252,7 @@ construct_runtime!( EVM: pallet_evm = 60, Ethereum: pallet_ethereum = 61, - BaseFee: pallet_base_fee = 62, + DynamicEvmBaseFee: pallet_dynamic_evm_base_fee = 62, EVMChainId: pallet_evm_chain_id = 63, EthereumChecked: pallet_ethereum_checked = 64, UnifiedAccounts: pallet_unified_accounts = 65, @@ -1307,17 +1305,40 @@ pub type Executive = frame_executive::Executive< Migrations, >; -// Used to cleanup StateTrieMigration storage - remove once cleanup is done. +// Used to cleanup BaseFee storage - remove once cleanup is done. parameter_types! { - pub const StateTrieMigrationStr: &'static str = "StateTrieMigration"; + pub const BaseFeeStr: &'static str = "BaseFee"; +} + +/// Simple `OnRuntimeUpgrade` logic to prepare Shibuya runtime for `DynamicEvmBaseFee` pallet. +pub use frame_support::traits::{OnRuntimeUpgrade, StorageVersion}; +pub struct DynamicEvmBaseFeeMigration; +impl OnRuntimeUpgrade for DynamicEvmBaseFeeMigration { + fn on_runtime_upgrade() -> Weight { + // Safety check to ensure we don't execute this migration twice + if pallet_dynamic_evm_base_fee::BaseFeePerGas::::exists() { + return ::DbWeight::get().reads(1); + } + + // Set the init value to what was set before on the old `BaseFee` pallet. + pallet_dynamic_evm_base_fee::BaseFeePerGas::::put(U256::from(1_000_000_000_u128)); + + // Shibuya's multiplier is so low that we have to set it to minimum value directly. + pallet_transaction_payment::NextFeeMultiplier::::put(MinimumMultiplier::get()); + + // Set init storage version for the pallet + StorageVersion::new(1).put::>(); + + ::DbWeight::get().reads_writes(1, 3) + } } /// All migrations that will run on the next runtime upgrade. /// /// Once done, migrations should be removed from the tuple. pub type Migrations = ( - frame_support::migrations::RemovePallet, - pallet_contracts::Migration, + frame_support::migrations::RemovePallet, + DynamicEvmBaseFeeMigration, ); type EventRecord = frame_system::EventRecord< @@ -1402,6 +1423,7 @@ mod benches { [pallet_xcm, PolkadotXcm] [pallet_ethereum_checked, EthereumChecked] [pallet_xvm, Xvm] + [pallet_dynamic_evm_base_fee, DynamicEvmBaseFee] [pallet_unified_accounts, UnifiedAccounts] ); } @@ -1765,7 +1787,7 @@ impl_runtime_apis! { } fn elasticity() -> Option { - Some(pallet_base_fee::Elasticity::::get()) + Some(Permill::zero()) } fn gas_limit_multiplier_support() {} diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index aa46e80b12..d0de6fb599 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -211,13 +211,17 @@ pub fn run_to_block(n: u32) { while System::block_number() < n { let block_number = System::block_number(); Timestamp::set_timestamp(block_number as u64 * BLOCK_TIME); + TransactionPayment::on_finalize(block_number); DappsStaking::on_finalize(block_number); Authorship::on_finalize(block_number); Session::on_finalize(block_number); AuraExt::on_finalize(block_number); PolkadotXcm::on_finalize(block_number); Ethereum::on_finalize(block_number); + #[cfg(any(feature = "shiden", features = "astar"))] BaseFee::on_finalize(block_number); + #[cfg(any(feature = "shibuya"))] + DynamicEvmBaseFee::on_finalize(block_number); System::set_block_number(block_number + 1); @@ -227,7 +231,10 @@ pub fn run_to_block(n: u32) { Aura::on_initialize(block_number); AuraExt::on_initialize(block_number); Ethereum::on_initialize(block_number); + #[cfg(any(feature = "shiden", features = "astar"))] BaseFee::on_initialize(block_number); + #[cfg(any(feature = "shibuya"))] + DynamicEvmBaseFee::on_initialize(block_number); #[cfg(any(feature = "shibuya", feature = "shiden", features = "astar"))] RandomnessCollectiveFlip::on_initialize(block_number); diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index 0ec64a74f7..bf179f5ab5 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -762,7 +762,7 @@ fn calling_wasm_from_evm_works_if_sufficient_storage_deposit_limit() { let wasm_callee_addr = deploy_wasm_contract(WASM_SIMPLE_STORAGE_NAME); let evm_caller_addr = deploy_evm_contract(CALL_XVM_PAYABLE_WITH_SDL); - // Fund the EVM caller to pay for storage deposit. + // Fund the EVM contract to pay for storage deposit. let _ = Balances::deposit_creating(&account_id_from(evm_caller_addr.clone()), UNIT); assert_ok!(EVM::call( @@ -772,8 +772,8 @@ fn calling_wasm_from_evm_works_if_sufficient_storage_deposit_limit() { // to: 0x0e0ddb5a5f0b99d7be468a3051a94073ec6b1900178316401a52b93415026999 // input: 0x0000002a (store) // value: 0 - // storage_deposit_limit: 1_000_000_000_000 - hex::decode("2d9338da000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8d4a5100000000000000000000000000000000000000000000000000000000000000000200e0ddb5a5f0b99d7be468a3051a94073ec6b1900178316401a52b9341502699900000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), + // storage_deposit_limit: 1_000_000_000_000_000 + hex::decode("2d9338da000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000200e0ddb5a5f0b99d7be468a3051a94073ec6b1900178316401a52b9341502699900000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), U256::zero(), 1_000_000, U256::from(DefaultBaseFeePerGas::get()),