diff --git a/Cargo.lock b/Cargo.lock
index e82eced0c5..20d049c330 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7153,6 +7153,8 @@ dependencies = [
"fp-evm",
"frame-support",
"frame-system",
+ "pallet-balances",
+ "pallet-timestamp",
"pallet-transaction-payment",
"parity-scale-codec",
"scale-info",
diff --git a/pallets/dynamic-evm-base-fee/Cargo.toml b/pallets/dynamic-evm-base-fee/Cargo.toml
index 6ba0e41c13..90dc93dfe5 100644
--- a/pallets/dynamic-evm-base-fee/Cargo.toml
+++ b/pallets/dynamic-evm-base-fee/Cargo.toml
@@ -15,13 +15,17 @@ scale-info = { workspace = true }
# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
+pallet-transaction-payment = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
-pallet-transaction-payment = { workspace = true }
# Frontier
fp-evm = { workspace = true }
+[dev-dependencies]
+pallet-balances = { workspace = true }
+pallet-timestamp = { workspace = true }
+
[features]
default = ["std"]
std = [
@@ -32,12 +36,14 @@ std = [
"frame-system/std",
"sp-core/std",
"sp-runtime/std",
- "pallet-transaction-payment/std",
+ "pallet-transaction-payment/std",
+ "pallet-balances/std",
+ "pallet-timestamp/std",
# Frontier
"fp-evm/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
- "pallet-transaction-payment/try-runtime",
+ "pallet-transaction-payment/try-runtime",
]
diff --git a/pallets/dynamic-evm-base-fee/src/lib.rs b/pallets/dynamic-evm-base-fee/src/lib.rs
index 03cbf9c040..b4a6fa4397 100644
--- a/pallets/dynamic-evm-base-fee/src/lib.rs
+++ b/pallets/dynamic-evm-base-fee/src/lib.rs
@@ -16,14 +16,21 @@
// You should have received a copy of the GNU General Public License
// along with Astar. If not, see .
+//! TODO: Rustdoc!!!
+
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{traits::Get, weights::Weight};
use sp_core::U256;
-use sp_runtime::{traits::UniqueSaturatedInto, Perquintill};
+use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128, Perquintill};
pub use self::pallet::*;
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
@@ -45,7 +52,7 @@ pub mod pallet {
/// 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;
+ 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.
@@ -87,10 +94,16 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
- /// New `base fee per gas` value has been forced-set.
+ /// 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 {
@@ -115,6 +128,7 @@ pub mod pallet {
// TODO: maybe add a DB entry to check until when should we apply max step adjustment?
// Once 'equilibrium' is reached, it's safe to just follow the formula without limit updates.
+ // Or we could abuse the sudo for this.
// Lower & upper limit between which the new base fee per gas should be clamped.
let lower_limit = T::MinBaseFeePerGas::get().max(old_bfpg.saturating_sub(max_step));
@@ -122,7 +136,8 @@ pub mod pallet {
// Calculate ideal new 'base_fee_per_gas' according to the formula
let ideal_new_bfpg = T::AdjustmentFactor::get()
- .saturating_mul(T::WeightFactor::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);
@@ -138,6 +153,11 @@ pub mod pallet {
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
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(())
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..19b3c45288
--- /dev/null
+++ b/pallets/dynamic-evm-base-fee/src/mock.rs
@@ -0,0 +1,167 @@
+// 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},
+ weights::Weight,
+};
+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 = ();
+ 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 WeightPerGas: Weight = Weight::from_parts(1, 0);
+ 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 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());
+}
+
+/// Helper function to get the adjustment factor used by the pallet.
+/// In case it's unset, it returns 0.
+pub fn get_adjustment_factor() -> FixedU128 {
+ storage::unhashed::get::(&ADJUSTMENT_FACTOR).unwrap_or_default()
+}
+
+pub struct GetAdjustmentFactor;
+impl Get for GetAdjustmentFactor {
+ fn get() -> FixedU128 {
+ get_adjustment_factor()
+ }
+}
+
+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
+ }
+}
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..c44f60a3f2
--- /dev/null
+++ b/pallets/dynamic-evm-base-fee/src/tests.rs
@@ -0,0 +1,91 @@
+// 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};
+use sp_runtime::traits::BadOrigin;
+
+#[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
+ );
+ });
+}