diff --git a/runtime/common/src/deal_with_fees.rs b/runtime/common/src/deal_with_fees.rs
new file mode 100644
index 0000000000..dd65f9c2d6
--- /dev/null
+++ b/runtime/common/src/deal_with_fees.rs
@@ -0,0 +1,114 @@
+// Copyright 2024 Moonbeam foundation
+// This file is part of Moonbeam.
+
+// Moonbeam is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Moonbeam is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Moonbeam. If not, see .
+
+use frame_support::__private::Get;
+use frame_support::pallet_prelude::TypedGet;
+use frame_support::traits::fungible::Credit;
+use frame_support::traits::tokens::imbalance::ResolveTo;
+use frame_support::traits::Imbalance;
+use frame_support::traits::OnUnbalanced;
+use pallet_treasury::TreasuryAccountId;
+use sp_runtime::Perbill;
+
+/// Deal with substrate based fees and tip. This should be used with pallet_transaction_payment.
+pub struct DealWithSubstrateFeesAndTip(
+ sp_std::marker::PhantomData<(R, FeesTreasuryProportion)>,
+);
+impl DealWithSubstrateFeesAndTip
+where
+ R: pallet_balances::Config + pallet_treasury::Config + pallet_author_inherent::Config,
+ pallet_author_inherent::Pallet: Get,
+ FeesTreasuryProportion: Get,
+{
+ fn deal_with_fees(amount: Credit>) {
+ // Balances pallet automatically burns dropped Credits by decreasing
+ // total_supply accordingly
+ let treasury_proportion = FeesTreasuryProportion::get();
+ let treasury_part = treasury_proportion.deconstruct();
+ let burn_part = Perbill::one().deconstruct() - treasury_part;
+ let (_, to_treasury) = amount.ration(burn_part, treasury_part);
+ ResolveTo::, pallet_balances::Pallet>::on_unbalanced(to_treasury);
+ }
+
+ fn deal_with_tip(amount: Credit>) {
+ ResolveTo::, pallet_balances::Pallet>::on_unbalanced(amount);
+ }
+}
+
+impl OnUnbalanced>>
+ for DealWithSubstrateFeesAndTip
+where
+ R: pallet_balances::Config + pallet_treasury::Config + pallet_author_inherent::Config,
+ pallet_author_inherent::Pallet: Get,
+ FeesTreasuryProportion: Get,
+{
+ fn on_unbalanceds(
+ mut fees_then_tips: impl Iterator- >>,
+ ) {
+ if let Some(fees) = fees_then_tips.next() {
+ Self::deal_with_fees(fees);
+ if let Some(tip) = fees_then_tips.next() {
+ Self::deal_with_tip(tip);
+ }
+ }
+ }
+}
+
+/// Deal with ethereum based fees. To handle tips/priority fees, use DealWithEthereumPriorityFees.
+pub struct DealWithEthereumBaseFees(
+ sp_std::marker::PhantomData<(R, FeesTreasuryProportion)>,
+);
+impl OnUnbalanced>>
+ for DealWithEthereumBaseFees
+where
+ R: pallet_balances::Config + pallet_treasury::Config,
+ FeesTreasuryProportion: Get,
+{
+ fn on_nonzero_unbalanced(amount: Credit>) {
+ // Balances pallet automatically burns dropped Credits by decreasing
+ // total_supply accordingly
+ let treasury_proportion = FeesTreasuryProportion::get();
+ let treasury_part = treasury_proportion.deconstruct();
+ let burn_part = Perbill::one().deconstruct() - treasury_part;
+ let (_, to_treasury) = amount.ration(burn_part, treasury_part);
+ ResolveTo::, pallet_balances::Pallet>::on_unbalanced(to_treasury);
+ }
+}
+
+pub struct BlockAuthorAccountId(sp_std::marker::PhantomData);
+impl TypedGet for BlockAuthorAccountId
+where
+ R: frame_system::Config + pallet_author_inherent::Config,
+ pallet_author_inherent::Pallet: Get,
+{
+ type Type = R::AccountId;
+ fn get() -> Self::Type {
+ as Get>::get()
+ }
+}
+
+/// Deal with ethereum based priority fees/tips. See DealWithEthereumBaseFees for base fees.
+pub struct DealWithEthereumPriorityFees(sp_std::marker::PhantomData);
+impl OnUnbalanced>>
+ for DealWithEthereumPriorityFees
+where
+ R: pallet_balances::Config + pallet_author_inherent::Config,
+ pallet_author_inherent::Pallet: Get,
+{
+ fn on_nonzero_unbalanced(amount: Credit>) {
+ ResolveTo::, pallet_balances::Pallet>::on_unbalanced(amount);
+ }
+}
diff --git a/runtime/common/src/impl_on_charge_evm_transaction.rs b/runtime/common/src/impl_on_charge_evm_transaction.rs
index 0fe3b9c026..1cb816c73c 100644
--- a/runtime/common/src/impl_on_charge_evm_transaction.rs
+++ b/runtime/common/src/impl_on_charge_evm_transaction.rs
@@ -22,13 +22,17 @@ macro_rules! impl_on_charge_evm_transaction {
type BalanceFor =
<::Currency as Inspect>>::Balance;
- pub struct OnChargeEVMTransaction(sp_std::marker::PhantomData);
+ pub struct OnChargeEVMTransaction(
+ sp_std::marker::PhantomData<(BaseFeesOU, PriorityFeesOU)>
+ );
- impl OnChargeEVMTransactionT for OnChargeEVMTransaction
+ impl OnChargeEVMTransactionT
+ for OnChargeEVMTransaction
where
T: pallet_evm::Config,
T::Currency: Balanced>,
- OU: OnUnbalanced, T::Currency>>,
+ BaseFeesOU: OnUnbalanced, T::Currency>>,
+ PriorityFeesOU: OnUnbalanced, T::Currency>>,
U256: UniqueSaturatedInto<>>::Balance>,
T::AddressMapping: pallet_evm::AddressMapping,
{
@@ -48,14 +52,14 @@ macro_rules! impl_on_charge_evm_transaction {
base_fee: U256,
already_withdrawn: Self::LiquidityInfo,
) -> Self::LiquidityInfo {
- ::Currency, OU> as OnChargeEVMTransactionT<
+ ::Currency, BaseFeesOU> as OnChargeEVMTransactionT<
T,
>>::correct_and_deposit_fee(who, corrected_fee, base_fee, already_withdrawn)
}
fn pay_priority_fee(tip: Self::LiquidityInfo) {
if let Some(tip) = tip {
- OU::on_unbalanced(tip);
+ PriorityFeesOU::on_unbalanced(tip);
}
}
}
diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs
index 735a565ce3..1df85e1e52 100644
--- a/runtime/common/src/lib.rs
+++ b/runtime/common/src/lib.rs
@@ -19,6 +19,7 @@
mod apis;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
+pub mod deal_with_fees;
mod impl_moonbeam_xcm_call;
mod impl_moonbeam_xcm_call_tracing;
mod impl_on_charge_evm_transaction;
diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs
index 073da4d764..ab9a1db6c8 100644
--- a/runtime/moonbase/src/lib.rs
+++ b/runtime/moonbase/src/lib.rs
@@ -64,10 +64,9 @@ use frame_support::{
parameter_types,
traits::{
fungible::{Balanced, Credit, HoldConsideration, Inspect},
- tokens::imbalance::ResolveTo,
tokens::{PayFromAccount, UnityAssetBalanceConversion},
ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse,
- EqualPrivilegeOnly, FindAuthor, Imbalance, InstanceFilter, LinearStoragePrice, OnFinalize,
+ EqualPrivilegeOnly, FindAuthor, InstanceFilter, LinearStoragePrice, OnFinalize,
OnUnbalanced,
},
weights::{
@@ -90,7 +89,6 @@ use pallet_evm::{
OnChargeEVMTransaction as OnChargeEVMTransactionT, Runner,
};
use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment};
-use pallet_treasury::TreasuryAccountId;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use runtime_params::*;
use scale_info::TypeInfo;
@@ -338,52 +336,6 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = moonbase_weights::pallet_balances::WeightInfo;
}
-pub struct DealWithFees(sp_std::marker::PhantomData);
-impl OnUnbalanced>> for DealWithFees
-where
- R: pallet_balances::Config + pallet_treasury::Config,
-{
- // this seems to be called for substrate-based transactions
- fn on_unbalanceds(
- mut fees_then_tips: impl Iterator
- >>,
- ) {
- if let Some(fees) = fees_then_tips.next() {
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = fees.ration(burn_part, treasury_part);
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
-
- // handle tip if there is one
- if let Some(tip) = fees_then_tips.next() {
- // for now we use the same burn/treasury strategy used for regular fees
- let (_, to_treasury) = tip.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
- }
- }
- }
-
- // this is called from pallet_evm for Ethereum-based transactions
- // (technically, it calls on_unbalanced, which calls this when non-zero)
- fn on_nonzero_unbalanced(amount: Credit>) {
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = amount.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(to_treasury);
- }
-}
-
pub struct LengthToFee;
impl WeightToFeePolynomial for LengthToFee {
type Balance = Balance;
@@ -408,7 +360,13 @@ impl WeightToFeePolynomial for LengthToFee {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type OnChargeTransaction = FungibleAdapter>;
+ type OnChargeTransaction = FungibleAdapter<
+ Balances,
+ DealWithSubstrateFeesAndTip<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >,
+ >;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = ConstantMultiplier>;
type LengthToFee = LengthToFee;
@@ -542,7 +500,10 @@ impl pallet_evm::Config for Runtime {
type PrecompilesType = MoonbasePrecompiles;
type PrecompilesValue = PrecompilesValue;
type ChainId = EthereumChainId;
- type OnChargeTransaction = OnChargeEVMTransaction>;
+ type OnChargeTransaction = OnChargeEVMTransaction<
+ DealWithEthereumBaseFees,
+ DealWithEthereumPriorityFees,
+ >;
type BlockGasLimit = BlockGasLimit;
type FindAuthor = FindAuthorAdapter;
type OnCreate = ();
@@ -1496,6 +1457,10 @@ pub type Executive = frame_executive::Executive<
#[cfg(feature = "runtime-benchmarks")]
use moonbeam_runtime_common::benchmarking::BenchmarkHelper;
+use moonbeam_runtime_common::deal_with_fees::{
+ DealWithEthereumBaseFees, DealWithEthereumPriorityFees, DealWithSubstrateFeesAndTip,
+};
+
#[cfg(feature = "runtime-benchmarks")]
mod benches {
frame_support::parameter_types! {
diff --git a/runtime/moonbase/tests/common/mod.rs b/runtime/moonbase/tests/common/mod.rs
index 19df108ab7..794988584f 100644
--- a/runtime/moonbase/tests/common/mod.rs
+++ b/runtime/moonbase/tests/common/mod.rs
@@ -364,6 +364,9 @@ pub fn set_parachain_inherent_data() {
use cumulus_primitives_core::PersistedValidationData;
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
+
let mut relay_sproof = RelayStateSproofBuilder::default();
relay_sproof.para_id = 100u32.into();
relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs
index 9cc5ef1a25..e9ab621fb2 100644
--- a/runtime/moonbase/tests/integration_test.rs
+++ b/runtime/moonbase/tests/integration_test.rs
@@ -77,12 +77,13 @@ use moonkit_xcm_primitives::AccountIdAssetIdConversion;
use nimbus_primitives::NimbusId;
use pallet_evm::PrecompileSet;
//use pallet_evm_precompileset_assets_erc20::{SELECTOR_LOG_APPROVAL, SELECTOR_LOG_TRANSFER};
+use moonbase_runtime::runtime_params::dynamic_params;
use pallet_moonbeam_foreign_assets::AssetStatus;
use pallet_transaction_payment::Multiplier;
use pallet_xcm_transactor::{Currency, CurrencyPayment, HrmpOperation, TransactWeights};
use parity_scale_codec::Encode;
use sha3::{Digest, Keccak256};
-use sp_core::{crypto::UncheckedFrom, ByteArray, Pair, H160, H256, U256};
+use sp_core::{crypto::UncheckedFrom, ByteArray, Get, Pair, H160, H256, U256};
use sp_runtime::{bounded_vec, DispatchError, ModuleError};
use std::cell::Cell;
use std::rc::Rc;
@@ -1881,6 +1882,7 @@ fn transfer_ed_0_evm() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
// EVM transfer
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(ALICE),
@@ -1933,7 +1935,7 @@ fn refund_ed_0_evm() {
}
#[test]
-fn author_does_not_receive_priority_fee() {
+fn author_does_receive_priority_fee() {
ExtBuilder::default()
.with_balances(vec![(
AccountId::from(BOB),
@@ -1943,6 +1945,7 @@ fn author_does_not_receive_priority_fee() {
.execute_with(|| {
// Some block author as seen by pallet-evm.
let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// Currently the default impl of the evm uses `deposit_into_existing`.
// If we were to use this implementation, and for an author to receive eventual tips,
// the account needs to be somehow initialized, otherwise the deposit would fail.
@@ -1961,8 +1964,10 @@ fn author_does_not_receive_priority_fee() {
access_list: Vec::new(),
})
.dispatch(::RuntimeOrigin::root()));
- // Author free balance didn't change.
- assert_eq!(Balances::free_balance(author), 100 * UNIT,);
+
+ let priority_fee = 200 * GIGAWEI * 21_000;
+ // Author free balance increased by priority fee.
+ assert_eq!(Balances::free_balance(author), 100 * UNIT + priority_fee,);
});
}
@@ -1983,6 +1988,8 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.build()
.execute_with(|| {
let issuance_before = ::Currency::total_issuance();
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// EVM transfer.
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(BOB),
@@ -1998,14 +2005,18 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.dispatch(::RuntimeOrigin::root()));
let issuance_after = ::Currency::total_issuance();
- // Fee is 1 * base_fee + tip.
- let fee = ((2 * BASE_FEE_GENISIS) * 21_000) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonbase_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee: Balance = BASE_FEE_GENISIS * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonbase_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
@@ -2026,6 +2037,7 @@ fn total_issuance_after_evm_transaction_without_priority_fee() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
let issuance_before = ::Currency::total_issuance();
// EVM transfer.
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
@@ -2045,13 +2057,18 @@ fn total_issuance_after_evm_transaction_without_priority_fee() {
// Fee is 1 GWEI base fee.
let base_fee = TransactionPaymentAsGasPrice::min_gas_price().0;
assert_eq!(base_fee.as_u128(), BASE_FEE_GENISIS); // hint in case following asserts fail
- let fee = (base_fee.as_u128() * 21_000u128) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonbase_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee: Balance = BASE_FEE_GENISIS * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonbase_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
@@ -2780,21 +2797,27 @@ fn substrate_based_fees_zero_txn_costs_only_base_extrinsic() {
#[test]
fn deal_with_fees_handles_tip() {
use frame_support::traits::OnUnbalanced;
- use moonbase_runtime::{DealWithFees, Treasury};
+ use moonbase_runtime::Treasury;
+ use moonbeam_runtime_common::deal_with_fees::DealWithSubstrateFeesAndTip;
ExtBuilder::default().build().execute_with(|| {
- // This test checks the functionality of the `DealWithFees` trait implementation in the runtime.
- // It simulates a scenario where a fee and a tip are issued to an account and ensures that the
- // treasury receives the correct amount (20% of the total), and the rest is burned (80%).
- //
- // The test follows these steps:
- // 1. It issues a fee of 100 and a tip of 1000.
- // 2. It checks the total supply before the fee and tip are dealt with, which should be 1_100.
- // 3. It checks that the treasury's balance is initially 0.
- // 4. It calls `DealWithFees::on_unbalanceds` with the fee and tip.
- // 5. It checks that the treasury's balance is now 220 (20% of the fee and tip).
- // 6. It checks that the total supply has decreased by 880 (80% of the fee and tip), indicating
- // that this amount was burned.
+ set_parachain_inherent_data();
+ // This test validates the functionality of the `DealWithSubstrateFeesAndTip` trait implementation
+ // in the Moonbeam runtime. It verifies that:
+ // - The correct proportion of the fee is sent to the treasury.
+ // - The remaining fee is burned (removed from the total supply).
+ // - The entire tip is sent to the block author.
+
+ // The test details:
+ // 1. Simulate issuing a `fee` of 100 and a `tip` of 1000.
+ // 2. Confirm the initial total supply is 1,100 (equal to the sum of the issued fee and tip).
+ // 3. Confirm the treasury's balance is initially 0.
+ // 4. Execute the `DealWithSubstrateFeesAndTip::on_unbalanceds` function with the `fee` and `tip`.
+ // 5. Validate that the treasury's balance has increased by 20% of the fee (based on FeesTreasuryProportion).
+ // 6. Validate that 80% of the fee is burned, and the total supply decreases accordingly.
+ // 7. Validate that the entire tip (100%) is sent to the block author (collator).
+
+ // Step 1: Issue the fee and tip amounts.
let fee = as frame_support::traits::fungible::Balanced<
AccountId,
>>::issue(100);
@@ -2802,18 +2825,41 @@ fn deal_with_fees_handles_tip() {
AccountId,
>>::issue(1000);
+ // Step 2: Validate the initial supply and balances.
let total_supply_before = Balances::total_issuance();
+ let block_author = pallet_author_inherent::Pallet::::get();
+ let block_author_balance_before = Balances::free_balance(&block_author);
assert_eq!(total_supply_before, 1_100);
assert_eq!(Balances::free_balance(&Treasury::account_id()), 0);
- DealWithFees::on_unbalanceds(vec![fee, tip].into_iter());
+ // Step 3: Execute the fees handling logic.
+ DealWithSubstrateFeesAndTip::<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >::on_unbalanceds(vec![fee, tip].into_iter());
+
+ // Step 4: Compute the split between treasury and burned fees based on FeesTreasuryProportion (20%).
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ let treasury_fee_part: Balance = treasury_proportion.mul_floor(100);
+ let burnt_fee_part: Balance = 100 - treasury_fee_part;
- // treasury should have received 20%
- assert_eq!(Balances::free_balance(&Treasury::account_id()), 220);
+ // Step 5: Validate the treasury received 20% of the fee.
+ assert_eq!(
+ Balances::free_balance(&Treasury::account_id()),
+ treasury_fee_part,
+ );
- // verify 80% burned
+ // Step 6: Verify that 80% of the fee was burned (removed from the total supply).
let total_supply_after = Balances::total_issuance();
- assert_eq!(total_supply_before - total_supply_after, 880);
+ assert_eq!(total_supply_before - total_supply_after, burnt_fee_part,);
+
+ // Step 7: Validate that the block author (collator) received 100% of the tip.
+ let block_author_balance_after = Balances::free_balance(&block_author);
+ assert_eq!(
+ block_author_balance_after - block_author_balance_before,
+ 1000,
+ );
});
}
diff --git a/runtime/moonbase/tests/runtime_apis.rs b/runtime/moonbase/tests/runtime_apis.rs
index 7cfb9dc103..66f1e0edce 100644
--- a/runtime/moonbase/tests/runtime_apis.rs
+++ b/runtime/moonbase/tests/runtime_apis.rs
@@ -216,6 +216,8 @@ fn ethereum_runtime_rpc_api_current_transaction_statuses() {
)])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
+
let _result = Executive::apply_extrinsic(unchecked_eth_tx(VALID_ETH_TX));
rpc_run_to_block(2);
let statuses =
@@ -273,6 +275,7 @@ fn ethereum_runtime_rpc_api_current_receipts() {
)])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
let _result = Executive::apply_extrinsic(unchecked_eth_tx(VALID_ETH_TX));
rpc_run_to_block(2);
let receipts = Runtime::current_receipts().expect("Receipts result.");
diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs
index b5816bede2..74be2eeeec 100644
--- a/runtime/moonbeam/src/lib.rs
+++ b/runtime/moonbeam/src/lib.rs
@@ -46,11 +46,9 @@ use frame_support::{
parameter_types,
traits::{
fungible::{Balanced, Credit, HoldConsideration, Inspect},
- tokens::imbalance::ResolveTo,
tokens::{PayFromAccount, UnityAssetBalanceConversion},
ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse,
- EqualPrivilegeOnly, Imbalance, InstanceFilter, LinearStoragePrice, OnFinalize,
- OnUnbalanced,
+ EqualPrivilegeOnly, InstanceFilter, LinearStoragePrice, OnFinalize, OnUnbalanced,
},
weights::{
constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, Weight, WeightToFeeCoefficient,
@@ -75,7 +73,6 @@ use pallet_evm::{
};
pub use pallet_parachain_staking::{weights::WeightInfo, InflationInfo, Range};
use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment};
-use pallet_treasury::TreasuryAccountId;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
@@ -332,52 +329,6 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = moonbeam_weights::pallet_balances::WeightInfo;
}
-pub struct DealWithFees(sp_std::marker::PhantomData);
-impl OnUnbalanced>> for DealWithFees
-where
- R: pallet_balances::Config + pallet_treasury::Config,
-{
- // this seems to be called for substrate-based transactions
- fn on_unbalanceds(
- mut fees_then_tips: impl Iterator
- >>,
- ) {
- if let Some(fees) = fees_then_tips.next() {
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = fees.ration(burn_part, treasury_part);
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
-
- // handle tip if there is one
- if let Some(tip) = fees_then_tips.next() {
- // for now we use the same burn/treasury strategy used for regular fees
- let (_, to_treasury) = tip.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
- }
- }
- }
-
- // this is called from pallet_evm for Ethereum-based transactions
- // (technically, it calls on_unbalanced, which calls this when non-zero)
- fn on_nonzero_unbalanced(amount: Credit>) {
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = amount.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(to_treasury);
- }
-}
-
pub struct LengthToFee;
impl WeightToFeePolynomial for LengthToFee {
type Balance = Balance;
@@ -402,7 +353,13 @@ impl WeightToFeePolynomial for LengthToFee {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type OnChargeTransaction = FungibleAdapter>;
+ type OnChargeTransaction = FungibleAdapter<
+ Balances,
+ DealWithSubstrateFeesAndTip<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >,
+ >;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = ConstantMultiplier>;
type LengthToFee = LengthToFee;
@@ -533,7 +490,10 @@ impl pallet_evm::Config for Runtime {
type PrecompilesType = MoonbeamPrecompiles;
type PrecompilesValue = PrecompilesValue;
type ChainId = EthereumChainId;
- type OnChargeTransaction = OnChargeEVMTransaction>;
+ type OnChargeTransaction = OnChargeEVMTransaction<
+ DealWithEthereumBaseFees,
+ DealWithEthereumPriorityFees,
+ >;
type BlockGasLimit = BlockGasLimit;
type FindAuthor = FindAuthorAdapter;
type OnCreate = ();
@@ -1491,6 +1451,10 @@ construct_runtime! {
#[cfg(feature = "runtime-benchmarks")]
use moonbeam_runtime_common::benchmarking::BenchmarkHelper;
+use moonbeam_runtime_common::deal_with_fees::{
+ DealWithEthereumBaseFees, DealWithEthereumPriorityFees, DealWithSubstrateFeesAndTip,
+};
+
#[cfg(feature = "runtime-benchmarks")]
mod benches {
frame_support::parameter_types! {
diff --git a/runtime/moonbeam/tests/common/mod.rs b/runtime/moonbeam/tests/common/mod.rs
index 9301dd123b..f83d7ad8a0 100644
--- a/runtime/moonbeam/tests/common/mod.rs
+++ b/runtime/moonbeam/tests/common/mod.rs
@@ -345,6 +345,9 @@ pub fn set_parachain_inherent_data() {
use cumulus_primitives_core::PersistedValidationData;
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
+
let mut relay_sproof = RelayStateSproofBuilder::default();
relay_sproof.para_id = 100u32.into();
relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs
index c56a3c2da1..ec8d54e953 100644
--- a/runtime/moonbeam/tests/integration_test.rs
+++ b/runtime/moonbeam/tests/integration_test.rs
@@ -33,6 +33,7 @@ use frame_support::{
StorageHasher, Twox128,
};
use moonbeam_runtime::currency::{GIGAWEI, WEI};
+use moonbeam_runtime::runtime_params::dynamic_params;
use moonbeam_runtime::{
asset_config::ForeignAssetInstance,
currency::GLMR,
@@ -57,7 +58,7 @@ use precompile_utils::{
testing::*,
};
use sha3::{Digest, Keccak256};
-use sp_core::{ByteArray, Pair, H160, U256};
+use sp_core::{ByteArray, Get, Pair, H160, U256};
use sp_runtime::{
traits::{Convert, Dispatchable},
BuildStorage, DispatchError, ModuleError,
@@ -1444,6 +1445,7 @@ fn transfer_ed_0_evm() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
// EVM transfer
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(ALICE),
@@ -1474,6 +1476,7 @@ fn refund_ed_0_evm() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
// EVM transfer that zeroes ALICE
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(ALICE),
@@ -1496,7 +1499,7 @@ fn refund_ed_0_evm() {
}
#[test]
-fn author_does_not_receive_priority_fee() {
+fn author_does_receive_priority_fee() {
ExtBuilder::default()
.with_balances(vec![(
AccountId::from(BOB),
@@ -1506,6 +1509,7 @@ fn author_does_not_receive_priority_fee() {
.execute_with(|| {
// Some block author as seen by pallet-evm.
let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// Currently the default impl of the evm uses `deposit_into_existing`.
// If we were to use this implementation, and for an author to receive eventual tips,
// the account needs to be somehow initialized, otherwise the deposit would fail.
@@ -1524,13 +1528,16 @@ fn author_does_not_receive_priority_fee() {
access_list: Vec::new(),
})
.dispatch(::RuntimeOrigin::root()));
- // Author free balance didn't change.
- assert_eq!(Balances::free_balance(author), 100 * GLMR,);
+
+ let priority_fee = 200 * GIGAWEI * 21_000;
+ // Author free balance increased by priority fee.
+ assert_eq!(Balances::free_balance(author), 100 * GLMR + priority_fee,);
});
}
#[test]
fn total_issuance_after_evm_transaction_with_priority_fee() {
+ use fp_evm::FeeCalculator;
ExtBuilder::default()
.with_balances(vec![
(
@@ -1545,6 +1552,8 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.build()
.execute_with(|| {
let issuance_before = ::Currency::total_issuance();
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// EVM transfer.
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(BOB),
@@ -1560,19 +1569,26 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.dispatch(::RuntimeOrigin::root()));
let issuance_after = ::Currency::total_issuance();
- // Fee is 25 GWEI base fee + 100 GWEI tip.
- let fee = ((125 * GIGAWEI) * 21_000) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonbeam_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee = TransactionPaymentAsGasPrice::min_gas_price().0.as_u128();
+
+ let base_fee: Balance = base_fee * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonbeam_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
#[test]
fn total_issuance_after_evm_transaction_without_priority_fee() {
+ use fp_evm::FeeCalculator;
ExtBuilder::default()
.with_balances(vec![
(
@@ -1586,6 +1602,7 @@ fn total_issuance_after_evm_transaction_without_priority_fee() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
let issuance_before = ::Currency::total_issuance();
// EVM transfer.
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
@@ -1602,14 +1619,20 @@ fn total_issuance_after_evm_transaction_without_priority_fee() {
.dispatch(::RuntimeOrigin::root()));
let issuance_after = ::Currency::total_issuance();
- // Fee is 100 GWEI base fee.
- let fee = (BASE_FEE_GENESIS * 21_000) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonbeam_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee = TransactionPaymentAsGasPrice::min_gas_price().0.as_u128();
+
+ let base_fee: Balance = base_fee * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonbeam_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
@@ -2595,21 +2618,27 @@ fn removed_precompiles() {
#[test]
fn deal_with_fees_handles_tip() {
use frame_support::traits::OnUnbalanced;
- use moonbeam_runtime::{DealWithFees, Treasury};
+ use moonbeam_runtime::Treasury;
+ use moonbeam_runtime_common::deal_with_fees::DealWithSubstrateFeesAndTip;
ExtBuilder::default().build().execute_with(|| {
- // This test checks the functionality of the `DealWithFees` trait implementation in the runtime.
- // It simulates a scenario where a fee and a tip are issued to an account and ensures that the
- // treasury receives the correct amount (20% of the total), and the rest is burned (80%).
- //
- // The test follows these steps:
- // 1. It issues a fee of 100 and a tip of 1000.
- // 2. It checks the total supply before the fee and tip are dealt with, which should be 1_100.
- // 3. It checks that the treasury's balance is initially 0.
- // 4. It calls `DealWithFees::on_unbalanceds` with the fee and tip.
- // 5. It checks that the treasury's balance is now 220 (20% of the fee and tip).
- // 6. It checks that the total supply has decreased by 880 (80% of the fee and tip), indicating
- // that this amount was burned.
+ set_parachain_inherent_data();
+ // This test validates the functionality of the `DealWithSubstrateFeesAndTip` trait implementation
+ // in the Moonbeam runtime. It verifies that:
+ // - The correct proportion of the fee is sent to the treasury.
+ // - The remaining fee is burned (removed from the total supply).
+ // - The entire tip is sent to the block author.
+
+ // The test details:
+ // 1. Simulate issuing a `fee` of 100 and a `tip` of 1000.
+ // 2. Confirm the initial total supply is 1,100 (equal to the sum of the issued fee and tip).
+ // 3. Confirm the treasury's balance is initially 0.
+ // 4. Execute the `DealWithSubstrateFeesAndTip::on_unbalanceds` function with the `fee` and `tip`.
+ // 5. Validate that the treasury's balance has increased by 20% of the fee (based on FeesTreasuryProportion).
+ // 6. Validate that 80% of the fee is burned, and the total supply decreases accordingly.
+ // 7. Validate that the entire tip (100%) is sent to the block author (collator).
+
+ // Step 1: Issue the fee and tip amounts.
let fee = as frame_support::traits::fungible::Balanced<
AccountId,
>>::issue(100);
@@ -2617,18 +2646,41 @@ fn deal_with_fees_handles_tip() {
AccountId,
>>::issue(1000);
+ // Step 2: Validate the initial supply and balances.
let total_supply_before = Balances::total_issuance();
+ let block_author = pallet_author_inherent::Pallet::::get();
+ let block_author_balance_before = Balances::free_balance(&block_author);
assert_eq!(total_supply_before, 1_100);
assert_eq!(Balances::free_balance(&Treasury::account_id()), 0);
- DealWithFees::on_unbalanceds(vec![fee, tip].into_iter());
+ // Step 3: Execute the fees handling logic.
+ DealWithSubstrateFeesAndTip::<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >::on_unbalanceds(vec![fee, tip].into_iter());
+
+ // Step 4: Compute the split between treasury and burned fees based on FeesTreasuryProportion (20%).
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
- // treasury should have received 20%
- assert_eq!(Balances::free_balance(&Treasury::account_id()), 220);
+ let treasury_fee_part: Balance = treasury_proportion.mul_floor(100);
+ let burnt_fee_part: Balance = 100 - treasury_fee_part;
- // verify 80% burned
+ // Step 5: Validate the treasury received 20% of the fee.
+ assert_eq!(
+ Balances::free_balance(&Treasury::account_id()),
+ treasury_fee_part,
+ );
+
+ // Step 6: Verify that 80% of the fee was burned (removed from the total supply).
let total_supply_after = Balances::total_issuance();
- assert_eq!(total_supply_before - total_supply_after, 880);
+ assert_eq!(total_supply_before - total_supply_after, burnt_fee_part,);
+
+ // Step 7: Validate that the block author (collator) received 100% of the tip.
+ let block_author_balance_after = Balances::free_balance(&block_author);
+ assert_eq!(
+ block_author_balance_after - block_author_balance_before,
+ 1000,
+ );
});
}
diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs
index 47e564ea5a..f0e5062258 100644
--- a/runtime/moonriver/src/lib.rs
+++ b/runtime/moonriver/src/lib.rs
@@ -47,11 +47,9 @@ use frame_support::{
parameter_types,
traits::{
fungible::{Balanced, Credit, HoldConsideration, Inspect},
- tokens::imbalance::ResolveTo,
tokens::{PayFromAccount, UnityAssetBalanceConversion},
ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse,
- EqualPrivilegeOnly, Imbalance, InstanceFilter, LinearStoragePrice, OnFinalize,
- OnUnbalanced,
+ EqualPrivilegeOnly, InstanceFilter, LinearStoragePrice, OnFinalize, OnUnbalanced,
},
weights::{
constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, Weight, WeightToFeeCoefficient,
@@ -65,6 +63,9 @@ pub use moonbeam_core_primitives::{
Index, Signature,
};
use moonbeam_rpc_primitives_txpool::TxPoolResponse;
+use moonbeam_runtime_common::deal_with_fees::{
+ DealWithEthereumBaseFees, DealWithEthereumPriorityFees, DealWithSubstrateFeesAndTip,
+};
use moonbeam_runtime_common::timestamp::{ConsensusHookWrapperForRelayTimestamp, RelayTimestamp};
pub use pallet_author_slot_filter::EligibilityValue;
use pallet_ethereum::Call::transact;
@@ -76,7 +77,6 @@ use pallet_evm::{
};
pub use pallet_parachain_staking::{weights::WeightInfo, InflationInfo, Range};
use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment};
-use pallet_treasury::TreasuryAccountId;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_api::impl_runtime_apis;
@@ -334,52 +334,6 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = moonriver_weights::pallet_balances::WeightInfo;
}
-pub struct DealWithFees(sp_std::marker::PhantomData);
-impl OnUnbalanced>> for DealWithFees
-where
- R: pallet_balances::Config + pallet_treasury::Config,
-{
- // this seems to be called for substrate-based transactions
- fn on_unbalanceds(
- mut fees_then_tips: impl Iterator
- >>,
- ) {
- if let Some(fees) = fees_then_tips.next() {
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = fees.ration(burn_part, treasury_part);
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
-
- // handle tip if there is one
- if let Some(tip) = fees_then_tips.next() {
- // for now we use the same burn/treasury strategy used for regular fees
- let (_, to_treasury) = tip.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(
- to_treasury,
- );
- }
- }
- }
-
- // this is called from pallet_evm for Ethereum-based transactions
- // (technically, it calls on_unbalanced, which calls this when non-zero)
- fn on_nonzero_unbalanced(amount: Credit>) {
- // Balances pallet automatically burns dropped Credits by decreasing
- // total_supply accordingly
- let treasury_proportion =
- runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion::get();
- let treasury_part = treasury_proportion.deconstruct();
- let burn_part = Perbill::one().deconstruct() - treasury_part;
- let (_, to_treasury) = amount.ration(burn_part, treasury_part);
- ResolveTo::, pallet_balances::Pallet>::on_unbalanced(to_treasury);
- }
-}
-
pub struct LengthToFee;
impl WeightToFeePolynomial for LengthToFee {
type Balance = Balance;
@@ -404,7 +358,13 @@ impl WeightToFeePolynomial for LengthToFee {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type OnChargeTransaction = FungibleAdapter>;
+ type OnChargeTransaction = FungibleAdapter<
+ Balances,
+ DealWithSubstrateFeesAndTip<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >,
+ >;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = ConstantMultiplier>;
type LengthToFee = LengthToFee;
@@ -535,7 +495,10 @@ impl pallet_evm::Config for Runtime {
type PrecompilesType = MoonriverPrecompiles;
type PrecompilesValue = PrecompilesValue;
type ChainId = EthereumChainId;
- type OnChargeTransaction = OnChargeEVMTransaction>;
+ type OnChargeTransaction = OnChargeEVMTransaction<
+ DealWithEthereumBaseFees,
+ DealWithEthereumPriorityFees,
+ >;
type BlockGasLimit = BlockGasLimit;
type FindAuthor = FindAuthorAdapter;
type OnCreate = ();
diff --git a/runtime/moonriver/tests/common/mod.rs b/runtime/moonriver/tests/common/mod.rs
index 249debb227..32d0dd8b33 100644
--- a/runtime/moonriver/tests/common/mod.rs
+++ b/runtime/moonriver/tests/common/mod.rs
@@ -352,6 +352,9 @@ pub fn set_parachain_inherent_data() {
use cumulus_primitives_core::PersistedValidationData;
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
+
let mut relay_sproof = RelayStateSproofBuilder::default();
relay_sproof.para_id = 100u32.into();
relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs
index 66787031e1..148e14c2a6 100644
--- a/runtime/moonriver/tests/integration_test.rs
+++ b/runtime/moonriver/tests/integration_test.rs
@@ -33,6 +33,7 @@ use frame_support::{
use moonbeam_xcm_benchmarks::weights::XcmWeight;
use moonkit_xcm_primitives::AccountIdAssetIdConversion;
use moonriver_runtime::currency::{GIGAWEI, WEI};
+use moonriver_runtime::runtime_params::dynamic_params;
use moonriver_runtime::{
asset_config::ForeignAssetInstance,
xcm_config::{CurrencyId, SelfReserve},
@@ -53,7 +54,7 @@ use precompile_utils::{
testing::*,
};
use sha3::{Digest, Keccak256};
-use sp_core::{ByteArray, Pair, H160, U256};
+use sp_core::{ByteArray, Get, Pair, H160, U256};
use sp_runtime::{
traits::{Convert, Dispatchable},
BuildStorage, DispatchError, ModuleError,
@@ -1428,6 +1429,7 @@ fn transfer_ed_0_evm() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
// EVM transfer
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(ALICE),
@@ -1458,6 +1460,7 @@ fn refund_ed_0_evm() {
])
.build()
.execute_with(|| {
+ set_parachain_inherent_data();
// EVM transfer that zeroes ALICE
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(ALICE),
@@ -1480,7 +1483,7 @@ fn refund_ed_0_evm() {
}
#[test]
-fn author_does_not_receive_priority_fee() {
+fn author_does_receive_priority_fee() {
ExtBuilder::default()
.with_balances(vec![(
AccountId::from(BOB),
@@ -1490,6 +1493,7 @@ fn author_does_not_receive_priority_fee() {
.execute_with(|| {
// Some block author as seen by pallet-evm.
let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// Currently the default impl of the evm uses `deposit_into_existing`.
// If we were to use this implementation, and for an author to receive eventual tips,
// the account needs to be somehow initialized, otherwise the deposit would fail.
@@ -1508,13 +1512,16 @@ fn author_does_not_receive_priority_fee() {
access_list: Vec::new(),
})
.dispatch(::RuntimeOrigin::root()));
- // Author free balance didn't change.
- assert_eq!(Balances::free_balance(author), 100 * MOVR,);
+
+ let priority_fee = 200 * GIGAWEI * 21_000;
+ // Author free balance increased by priority fee.
+ assert_eq!(Balances::free_balance(author), 100 * MOVR + priority_fee,);
});
}
#[test]
fn total_issuance_after_evm_transaction_with_priority_fee() {
+ use fp_evm::FeeCalculator;
ExtBuilder::default()
.with_balances(vec![
(
@@ -1529,6 +1536,8 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.build()
.execute_with(|| {
let issuance_before = ::Currency::total_issuance();
+ let author = AccountId::from(>::find_author());
+ pallet_author_inherent::Author::::put(author);
// EVM transfer.
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call {
source: H160::from(BOB),
@@ -1544,18 +1553,26 @@ fn total_issuance_after_evm_transaction_with_priority_fee() {
.dispatch(::RuntimeOrigin::root()));
let issuance_after = ::Currency::total_issuance();
- let fee = ((2 * BASE_FEE_GENESIS) * 21_000) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonriver_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee = TransactionPaymentAsGasPrice::min_gas_price().0.as_u128();
+
+ let base_fee: Balance = base_fee * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonriver_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
#[test]
fn total_issuance_after_evm_transaction_without_priority_fee() {
+ use fp_evm::FeeCalculator;
ExtBuilder::default()
.with_balances(vec![
(
@@ -1578,20 +1595,27 @@ fn total_issuance_after_evm_transaction_without_priority_fee() {
value: (1 * MOVR).into(),
gas_limit: 21_000u64,
max_fee_per_gas: U256::from(BASE_FEE_GENESIS),
- max_priority_fee_per_gas: Some(U256::from(BASE_FEE_GENESIS)),
+ max_priority_fee_per_gas: None,
nonce: Some(U256::from(0)),
access_list: Vec::new(),
})
.dispatch(::RuntimeOrigin::root()));
let issuance_after = ::Currency::total_issuance();
- let fee = ((1 * BASE_FEE_GENESIS) * 21_000) as f64;
- // 80% was burned.
- let expected_burn = (fee * 0.8) as u128;
- assert_eq!(issuance_after, issuance_before - expected_burn,);
- // 20% was sent to treasury.
- let expected_treasury = (fee * 0.2) as u128;
- assert_eq!(moonriver_runtime::Treasury::pot(), expected_treasury);
+
+ let base_fee = TransactionPaymentAsGasPrice::min_gas_price().0.as_u128();
+
+ let base_fee: Balance = base_fee * 21_000;
+
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
+
+ // only base fee is split between being burned and sent to treasury
+ let treasury_base_fee_part: Balance = treasury_proportion.mul_floor(base_fee);
+ let burnt_base_fee_part: Balance = base_fee - treasury_base_fee_part;
+
+ assert_eq!(issuance_after, issuance_before - burnt_base_fee_part);
+
+ assert_eq!(moonriver_runtime::Treasury::pot(), treasury_base_fee_part);
});
}
@@ -2493,21 +2517,27 @@ fn removed_precompiles() {
#[test]
fn deal_with_fees_handles_tip() {
use frame_support::traits::OnUnbalanced;
- use moonriver_runtime::{DealWithFees, Treasury};
+ use moonbeam_runtime_common::deal_with_fees::DealWithSubstrateFeesAndTip;
+ use moonriver_runtime::Treasury;
ExtBuilder::default().build().execute_with(|| {
- // This test checks the functionality of the `DealWithFees` trait implementation in the runtime.
- // It simulates a scenario where a fee and a tip are issued to an account and ensures that the
- // treasury receives the correct amount (20% of the total), and the rest is burned (80%).
- //
- // The test follows these steps:
- // 1. It issues a fee of 100 and a tip of 1000.
- // 2. It checks the total supply before the fee and tip are dealt with, which should be 1_100.
- // 3. It checks that the treasury's balance is initially 0.
- // 4. It calls `DealWithFees::on_unbalanceds` with the fee and tip.
- // 5. It checks that the treasury's balance is now 220 (20% of the fee and tip).
- // 6. It checks that the total supply has decreased by 880 (80% of the fee and tip), indicating
- // that this amount was burned.
+ set_parachain_inherent_data();
+ // This test validates the functionality of the `DealWithSubstrateFeesAndTip` trait implementation
+ // in the Moonriver runtime. It verifies that:
+ // - The correct proportion of the fee is sent to the treasury.
+ // - The remaining fee is burned (removed from the total supply).
+ // - The entire tip is sent to the block author.
+
+ // The test details:
+ // 1. Simulate issuing a `fee` of 100 and a `tip` of 1000.
+ // 2. Confirm the initial total supply is 1,100 (equal to the sum of the issued fee and tip).
+ // 3. Confirm the treasury's balance is initially 0.
+ // 4. Execute the `DealWithSubstrateFeesAndTip::on_unbalanceds` function with the `fee` and `tip`.
+ // 5. Validate that the treasury's balance has increased by 20% of the fee (based on FeesTreasuryProportion).
+ // 6. Validate that 80% of the fee is burned, and the total supply decreases accordingly.
+ // 7. Validate that the entire tip (100%) is sent to the block author (collator).
+
+ // Step 1: Issue the fee and tip amounts.
let fee = as frame_support::traits::fungible::Balanced<
AccountId,
>>::issue(100);
@@ -2515,18 +2545,41 @@ fn deal_with_fees_handles_tip() {
AccountId,
>>::issue(1000);
+ // Step 2: Validate the initial supply and balances.
let total_supply_before = Balances::total_issuance();
+ let block_author = pallet_author_inherent::Pallet::::get();
+ let block_author_balance_before = Balances::free_balance(&block_author);
assert_eq!(total_supply_before, 1_100);
assert_eq!(Balances::free_balance(&Treasury::account_id()), 0);
- DealWithFees::on_unbalanceds(vec![fee, tip].into_iter());
+ // Step 3: Execute the fees handling logic.
+ DealWithSubstrateFeesAndTip::<
+ Runtime,
+ dynamic_params::runtime_config::FeesTreasuryProportion,
+ >::on_unbalanceds(vec![fee, tip].into_iter());
+
+ // Step 4: Compute the split between treasury and burned fees based on FeesTreasuryProportion (20%).
+ let treasury_proportion = dynamic_params::runtime_config::FeesTreasuryProportion::get();
- // treasury should have received 20%
- assert_eq!(Balances::free_balance(&Treasury::account_id()), 220);
+ let treasury_fee_part: Balance = treasury_proportion.mul_floor(100);
+ let burnt_fee_part: Balance = 100 - treasury_fee_part;
- // verify 80% burned
+ // Step 5: Validate the treasury received 20% of the fee.
+ assert_eq!(
+ Balances::free_balance(&Treasury::account_id()),
+ treasury_fee_part,
+ );
+
+ // Step 6: Verify that 80% of the fee was burned (removed from the total supply).
let total_supply_after = Balances::total_issuance();
- assert_eq!(total_supply_before - total_supply_after, 880);
+ assert_eq!(total_supply_before - total_supply_after, burnt_fee_part,);
+
+ // Step 7: Validate that the block author (collator) received 100% of the tip.
+ let block_author_balance_after = Balances::free_balance(&block_author);
+ assert_eq!(
+ block_author_balance_after - block_author_balance_before,
+ 1000,
+ );
});
}
diff --git a/test/helpers/block.ts b/test/helpers/block.ts
index a2ab88991f..076c3ba34d 100644
--- a/test/helpers/block.ts
+++ b/test/helpers/block.ts
@@ -4,7 +4,6 @@ import {
type BlockRangeOption,
EXTRINSIC_BASE_WEIGHT,
WEIGHT_PER_GAS,
- calculateFeePortions,
mapExtrinsics,
} from "@moonwall/util";
import type { ApiPromise } from "@polkadot/api";
@@ -17,6 +16,8 @@ import type { AccountId20, Block } from "@polkadot/types/interfaces/runtime/type
import chalk from "chalk";
import type { Debugger } from "debug";
import Debug from "debug";
+import { calculateFeePortions, split } from "./fees.ts";
+import { getFeesTreasuryProportion } from "./parameters.ts";
const debug = Debug("test:blocks");
@@ -149,6 +150,8 @@ export const verifyBlockFees = async (
? (event.data[0] as DispatchInfo)
: (event.data[1] as DispatchInfo);
+ const feesTreasuryProportion = await getFeesTreasuryProportion(context);
+
// We are only interested in fee paying extrinsics:
// Either ethereum transactions or signed extrinsics with fees (substrate tx)
if (
@@ -175,40 +178,47 @@ export const verifyBlockFees = async (
const baseFeePerGas = (
await context.viem().getBlock({ blockNumber: BigInt(number - 1) })
).baseFeePerGas!;
- let priorityFee;
+ let priorityFee;
+ let gasFee;
// Transaction is an enum now with as many variants as supported transaction types.
if (ethTxWrapper.isLegacy) {
priorityFee = ethTxWrapper.asLegacy.gasPrice.toBigInt();
+ gasFee = priorityFee;
} else if (ethTxWrapper.isEip2930) {
priorityFee = ethTxWrapper.asEip2930.gasPrice.toBigInt();
+ gasFee = priorityFee;
} else if (ethTxWrapper.isEip1559) {
priorityFee = ethTxWrapper.asEip1559.maxPriorityFeePerGas.toBigInt();
+ gasFee = ethTxWrapper.asEip1559.maxFeePerGas.toBigInt();
}
- let effectiveTipPerGas = priorityFee - baseFeePerGas;
- if (effectiveTipPerGas < 0n) {
- effectiveTipPerGas = 0n;
+ let effectiveTipPerGas = gasFee - baseFeePerGas;
+ if (effectiveTipPerGas > priorityFee) {
+ effectiveTipPerGas = priorityFee;
}
- // Calculate the fees paid for base fee independently from tip fee. Both are subject
- // to 80/20 split (burn/treasury) but calculating these over the sum of the two
- // rather than independently leads to off-by-one errors.
+ // Calculate the fees paid for the base fee and tip fee independently.
+ // Only the base fee is subject to the split between burn and treasury.
const baseFeesPaid = gasUsed * baseFeePerGas;
const tipAsFeesPaid = gasUsed * effectiveTipPerGas;
- const baseFeePortions = calculateFeePortions(baseFeesPaid);
- const tipFeePortions = calculateFeePortions(tipAsFeesPaid);
+ const { burnt: baseFeePortionsBurnt } = calculateFeePortions(
+ feesTreasuryProportion,
+ baseFeesPaid
+ );
txFees += baseFeesPaid + tipAsFeesPaid;
- txBurnt += baseFeePortions.burnt;
- txBurnt += tipFeePortions.burnt;
+ txBurnt += baseFeePortionsBurnt;
} else {
// For a regular substrate tx, we use the partialFee
- const feePortions = calculateFeePortions(fee.partialFee.toBigInt());
- const tipPortions = calculateFeePortions(extrinsic.tip.toBigInt());
+ const feePortions = calculateFeePortions(
+ feesTreasuryProportion,
+ fee.partialFee.toBigInt()
+ );
+
txFees += fee.partialFee.toBigInt() + extrinsic.tip.toBigInt();
- txBurnt += feePortions.burnt + tipPortions.burnt;
+ txBurnt += feePortions.burnt;
// verify entire substrate txn fee
const apiAt = await context.polkadotJs().at(previousBlockHash);
@@ -239,11 +249,12 @@ export const verifyBlockFees = async (
})) as any
).toBigInt();
- // const tip = extrinsic.tip.toBigInt();
+ const tip = extrinsic.tip.toBigInt();
const expectedPartialFee = lengthFee + weightFee + baseFee;
- // Verify the computed fees are equal to the actual fees
- expect(expectedPartialFee).to.eq((paymentEvent!.data[1] as u128).toBigInt());
+ // Verify the computed fees are equal to the actual fees + tip
+ expect(expectedPartialFee + tip).to.eq((paymentEvent!.data[1] as u128).toBigInt());
+ expect(tip).to.eq((paymentEvent!.data[2] as u128).toBigInt());
// Verify the computed fees are equal to the rpc computed fees
expect(expectedPartialFee).to.eq(fee.partialFee.toBigInt());
diff --git a/test/helpers/fees.ts b/test/helpers/fees.ts
new file mode 100644
index 0000000000..2b832c77d9
--- /dev/null
+++ b/test/helpers/fees.ts
@@ -0,0 +1,31 @@
+import { BN } from "@polkadot/util";
+
+/// Recreation of fees.ration(burn_part, treasury_part)
+export const split = (value: BN, part1: BN, part2: BN): [BN, BN] => {
+ const total = part1.add(part2);
+ if (total.eq(new BN(0)) || value.eq(new BN(0))) {
+ return [new BN(0), new BN(0)];
+ }
+ const part1BN = value.mul(part1).div(total);
+ const part2BN = value.sub(part1BN);
+ return [part1BN, part2BN];
+};
+
+export const calculateFeePortions = (
+ feesTreasuryProportion: bigint,
+ fees: bigint
+): {
+ burnt: bigint;
+ treasury: bigint;
+} => {
+ const feesBN = new BN(fees.toString());
+ const treasuryPartBN = new BN(feesTreasuryProportion.toString());
+ const burntPartBN = new BN(1e9).sub(treasuryPartBN);
+
+ const [burntBN, treasuryBN] = split(feesBN, burntPartBN, treasuryPartBN);
+
+ return {
+ burnt: BigInt(burntBN.toString()),
+ treasury: BigInt(treasuryBN.toString()),
+ };
+};
diff --git a/test/helpers/index.ts b/test/helpers/index.ts
index 3c64aadda8..71efcf002a 100644
--- a/test/helpers/index.ts
+++ b/test/helpers/index.ts
@@ -24,3 +24,5 @@ export * from "./voting";
export * from "./wormhole";
export * from "./xcm";
export * from "./tracingFns";
+export * from "./fees.ts";
+export * from "./parameters.ts";
diff --git a/test/helpers/parameters.ts b/test/helpers/parameters.ts
new file mode 100644
index 0000000000..1bdb6d0c5c
--- /dev/null
+++ b/test/helpers/parameters.ts
@@ -0,0 +1,14 @@
+import type { DevModeContext } from "@moonwall/cli";
+
+export const getFeesTreasuryProportion = async (context: DevModeContext): Promise => {
+ const parameter = await context.polkadotJs().query.parameters.parameters({
+ RuntimeConfig: "FeesTreasuryProportion",
+ });
+
+ // 20% default value
+ let feesTreasuryProportion = 200_000_000n;
+ if (parameter.isSome) {
+ feesTreasuryProportion = parameter.value.asRuntimeConfig.asFeesTreasuryProportion.toBigInt();
+ }
+ return feesTreasuryProportion;
+};
diff --git a/test/scripts/update-local-types.ts b/test/scripts/update-local-types.ts
index 8e9945dfb2..d143bb741d 100644
--- a/test/scripts/update-local-types.ts
+++ b/test/scripts/update-local-types.ts
@@ -60,6 +60,7 @@ const startNode = (network: string, rpcPort: string, port: string) => {
"--wasm-execution=interpreted-i-know-what-i-do",
"--no-telemetry",
"--no-prometheus",
+ "--rpc-cors=all",
"--tmp",
],
{
@@ -107,7 +108,7 @@ const executeUpdateAPIScript = async () => {
// Bundle types
await executeScript("../../moonbeam-types-bundle", "pnpm i");
await executeScript("../../moonbeam-types-bundle", "pnpm build");
- await executeScript("../../moonbeam-types-bundle", "pnpm fmt:fix");
+ await executeScript("../../moonbeam-types-bundle", "pnpm check:fix");
// Generate types
console.log("Extracting metadata for all runtimes...");
diff --git a/test/suites/dev/moonbase/test-balance/test-balance-transfer.ts b/test/suites/dev/moonbase/test-balance/test-balance-transfer.ts
index b4cc85ae45..13608adc3d 100644
--- a/test/suites/dev/moonbase/test-balance/test-balance-transfer.ts
+++ b/test/suites/dev/moonbase/test-balance/test-balance-transfer.ts
@@ -2,6 +2,7 @@ import "@moonbeam-network/api-augment";
import { beforeEach, describeSuite, expect } from "@moonwall/cli";
import {
ALITH_ADDRESS,
+ CHARLETH_ADDRESS,
BALTATHAR_ADDRESS,
BALTATHAR_PRIVATE_KEY,
CHARLETH_PRIVATE_KEY,
@@ -77,16 +78,17 @@ describeSuite({
id: "T03",
title: "should decrease from account",
test: async function () {
- const balanceBefore = await context.viem().getBalance({ address: ALITH_ADDRESS });
+ const balanceBefore = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
const fees = 21000n * GENESIS_BASE_FEE;
await context.createBlock(
await createRawTransfer(context, randomAddress, 512n, {
gas: 21000n,
gasPrice: GENESIS_BASE_FEE,
txnType: "legacy",
+ privateKey: CHARLETH_PRIVATE_KEY,
})
);
- const balanceAfter = await context.viem().getBalance({ address: ALITH_ADDRESS });
+ const balanceAfter = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(balanceBefore - balanceAfter - fees).toBe(512n);
},
});
@@ -190,7 +192,7 @@ describeSuite({
id: "T08",
title: "should handle max_fee_per_gas",
test: async function () {
- const balanceBefore = await checkBalance(context);
+ const balanceBefore = await checkBalance(context, CHARLETH_ADDRESS);
await context.createBlock(
await createRawTransfer(context, randomAddress, 1n * GLMR, {
gas: 21000n,
@@ -198,9 +200,10 @@ describeSuite({
maxPriorityFeePerGas: parseGwei("0.2"),
gasPrice: GENESIS_BASE_FEE,
type: "eip1559",
+ privateKey: CHARLETH_PRIVATE_KEY,
})
);
- const balanceAfter = await checkBalance(context);
+ const balanceAfter = await checkBalance(context, CHARLETH_ADDRESS);
const fee = 21000n * GENESIS_BASE_FEE;
expect(balanceAfter + fee + 1n * GLMR).toBe(balanceBefore);
diff --git a/test/suites/dev/moonbase/test-contract/test-contract-error.ts b/test/suites/dev/moonbase/test-contract/test-contract-error.ts
index 67c2563ccd..e886d92f1a 100644
--- a/test/suites/dev/moonbase/test-contract/test-contract-error.ts
+++ b/test/suites/dev/moonbase/test-contract/test-contract-error.ts
@@ -6,7 +6,12 @@ import {
describeSuite,
expect,
} from "@moonwall/cli";
-import { ALITH_ADDRESS, createEthersTransaction } from "@moonwall/util";
+import {
+ CHARLETH_PRIVATE_KEY,
+ CHARLETH_ADDRESS,
+ createEthersTransaction,
+ ALITH_ADDRESS,
+} from "@moonwall/util";
import { encodeFunctionData, type Abi } from "viem";
import { verifyLatestBlockFees } from "../../../../helpers";
@@ -31,10 +36,10 @@ describeSuite({
id: `T0${TransactionTypes.indexOf(txnType) + 1}`,
title: `"should return OutOfGas on inifinite loop ${txnType} call`,
test: async function () {
- expect(
+ await expect(
async () =>
await context.viem().call({
- account: ALITH_ADDRESS,
+ account: CHARLETH_ADDRESS,
to: looperAddress,
data: encodeFunctionData({ abi: looperAbi, functionName: "infinite", args: [] }),
gas: 12_000_000n,
@@ -45,16 +50,25 @@ describeSuite({
});
it({
- id: `T0${TransactionTypes.indexOf(txnType) * 2 + 1}`,
+ id: `T0${TransactionTypes.indexOf(txnType) + 1 + TransactionTypes.length}`,
title: `should fail with OutOfGas on infinite loop ${txnType} transaction`,
test: async function () {
+ const nonce = await context.viem().getTransactionCount({ address: CHARLETH_ADDRESS });
+
const rawSigned = await createEthersTransaction(context, {
to: looperAddress,
data: encodeFunctionData({ abi: looperAbi, functionName: "infinite", args: [] }),
txnType,
+ nonce,
+ privateKey: CHARLETH_PRIVATE_KEY,
+ });
+
+ const { result } = await context.createBlock(rawSigned, {
+ signer: { type: "ethereum", privateKey: CHARLETH_PRIVATE_KEY },
});
- const { result } = await context.createBlock(rawSigned);
+ expect(result.successful).to.be.true;
+
const receipt = await context
.viem("public")
.getTransactionReceipt({ hash: result!.hash as `0x${string}` });
@@ -63,16 +77,24 @@ describeSuite({
});
it({
- id: `T0${TransactionTypes.indexOf(txnType) * 3 + 1}`,
+ id: `T0${TransactionTypes.indexOf(txnType) + 1 + TransactionTypes.length * 2}`,
title: `should fail with OutOfGas on infinite loop ${txnType} transaction - check fees`,
test: async function () {
+ const nonce = await context.viem().getTransactionCount({ address: CHARLETH_ADDRESS });
+
const rawSigned = await createEthersTransaction(context, {
to: looperAddress,
data: encodeFunctionData({ abi: looperAbi, functionName: "infinite", args: [] }),
txnType,
+ nonce,
+ privateKey: CHARLETH_PRIVATE_KEY,
+ });
+
+ const { result } = await context.createBlock(rawSigned, {
+ signer: { type: "ethereum", privateKey: CHARLETH_PRIVATE_KEY },
});
- await context.createBlock(rawSigned);
+ expect(result.successful).to.be.true;
await verifyLatestBlockFees(context);
},
});
diff --git a/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-error.ts b/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-error.ts
index dbb560dced..b026a5ca48 100644
--- a/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-error.ts
+++ b/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-error.ts
@@ -13,6 +13,7 @@ import {
} from "@moonwall/util";
import { parseGwei } from "viem";
import { ALITH_GENESIS_TRANSFERABLE_BALANCE, ConstantStore } from "../../../../helpers";
+import { UNIT } from "../test-parameters/test-parameters";
describeSuite({
id: "D011102",
@@ -155,8 +156,12 @@ describeSuite({
id: "T07",
title: "insufficient funds for gas * price + value",
test: async function () {
- const amount = ALITH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 10_000_000_000n + 1n;
- const tx = await createRawTransfer(context, BALTATHAR_ADDRESS, amount);
+ const CHARLETH_GENESIS_TRANSFERABLE_BALANCE =
+ ALITH_GENESIS_TRANSFERABLE_BALANCE + 1000n * UNIT + 10n * 100_000_000_000_000n;
+ const amount = CHARLETH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 10_000_000_000n + 1n;
+ const tx = await createRawTransfer(context, BALTATHAR_ADDRESS, amount, {
+ privateKey: CHARLETH_PRIVATE_KEY,
+ });
expect(
async () => await customDevRpcRequest("eth_sendRawTransaction", [tx])
diff --git a/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-resubmit-txn.ts b/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-resubmit-txn.ts
index b5ba7380dd..80f95da08c 100644
--- a/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-resubmit-txn.ts
+++ b/test/suites/dev/moonbase/test-eth-pool/test-eth-pool-resubmit-txn.ts
@@ -1,5 +1,11 @@
import { beforeEach, describeSuite, expect } from "@moonwall/cli";
-import { ALITH_ADDRESS, createRawTransfer, GLMR, sendRawTransaction } from "@moonwall/util";
+import {
+ CHARLETH_ADDRESS,
+ CHARLETH_PRIVATE_KEY,
+ createRawTransfer,
+ GLMR,
+ sendRawTransaction,
+} from "@moonwall/util";
import { parseGwei } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
@@ -10,10 +16,14 @@ describeSuite({
testCases: ({ context, it, log }) => {
let randomAddress: `0x${string}`;
let currentNonce: number;
+ let actorPrivateKey: `0x${string}`;
+ let actorAddress: `0x${string}`;
beforeEach(async function () {
+ actorPrivateKey = CHARLETH_PRIVATE_KEY;
+ actorAddress = CHARLETH_ADDRESS;
randomAddress = privateKeyToAccount(generatePrivateKey()).address;
- currentNonce = await context.viem().getTransactionCount({ address: ALITH_ADDRESS });
+ currentNonce = await context.viem().getTransactionCount({ address: actorAddress });
});
it({
@@ -25,11 +35,13 @@ describeSuite({
nonce: currentNonce,
maxFeePerGas: parseGwei("300"),
maxPriorityFeePerGas: parseGwei("300"),
+ privateKey: actorPrivateKey,
}),
await createRawTransfer(context, randomAddress, 2, {
nonce: currentNonce,
maxFeePerGas: parseGwei("400"),
maxPriorityFeePerGas: parseGwei("300"),
+ privateKey: actorPrivateKey,
// same priority fee but higher max fee so higher tip
}),
]);
@@ -45,10 +57,12 @@ describeSuite({
await createRawTransfer(context, randomAddress, 1, {
nonce: currentNonce,
maxFeePerGas: parseGwei("20"),
+ privateKey: actorPrivateKey,
}),
await createRawTransfer(context, randomAddress, 2, {
nonce: currentNonce,
maxFeePerGas: parseGwei("10"),
+ privateKey: actorPrivateKey,
}),
]);
expect(await context.viem().getBalance({ address: randomAddress })).to.equal(1n);
@@ -65,11 +79,13 @@ describeSuite({
nonce: currentNonce,
maxFeePerGas: parseGwei("10"),
gas: 1048575n,
+ privateKey: actorPrivateKey,
}),
await createRawTransfer(context, randomAddress, 2, {
nonce: currentNonce,
maxFeePerGas: parseGwei("20"),
gas: 65536n,
+ privateKey: actorPrivateKey,
}),
]);
@@ -82,13 +98,14 @@ describeSuite({
title: "should prioritize higher gas tips",
test: async function () {
// GasFee are using very high value to ensure gasPrice is not impacting
- const alithGLMR = (await context.viem().getBalance({ address: ALITH_ADDRESS })) / GLMR;
+ const addressGLMR = (await context.viem().getBalance({ address: actorAddress })) / GLMR;
await sendRawTransaction(
context,
await createRawTransfer(context, randomAddress, 66, {
nonce: currentNonce,
maxFeePerGas: 1n * GLMR,
maxPriorityFeePerGas: 1n * GLMR,
+ privateKey: actorPrivateKey,
})
);
@@ -100,14 +117,15 @@ describeSuite({
nonce: currentNonce,
maxFeePerGas: gasPrice,
maxPriorityFeePerGas: gasPrice,
+ privateKey: actorPrivateKey,
})
)
);
await context.createBlock(txns);
- expect((await context.viem().getBalance({ address: ALITH_ADDRESS })) / GLMR).to.equal(
- alithGLMR - 21000n * 20n
+ expect((await context.viem().getBalance({ address: actorAddress })) / GLMR).to.equal(
+ addressGLMR - 21000n * 20n
);
expect(await context.viem().getBalance({ address: randomAddress })).to.equal(77n);
},
@@ -122,11 +140,13 @@ describeSuite({
nonce: currentNonce,
maxFeePerGas: parseGwei("300"),
maxPriorityFeePerGas: parseGwei("10"),
+ privateKey: actorPrivateKey,
}),
await createRawTransfer(context, randomAddress, 2, {
nonce: currentNonce,
maxFeePerGas: parseGwei("400"),
maxPriorityFeePerGas: parseGwei("10"),
+ privateKey: actorPrivateKey,
}),
]);
expect(await context.viem().getBalance({ address: randomAddress })).to.equal(1n);
diff --git a/test/suites/dev/moonbase/test-fees/test-fees-repartition.ts b/test/suites/dev/moonbase/test-fees/test-fees-repartition.ts
index 81ee135eed..cd6047c78c 100644
--- a/test/suites/dev/moonbase/test-fees/test-fees-repartition.ts
+++ b/test/suites/dev/moonbase/test-fees/test-fees-repartition.ts
@@ -2,6 +2,7 @@ import "@moonbeam-network/api-augment";
import { TransactionTypes, describeSuite, expect } from "@moonwall/cli";
import { BALTATHAR_ADDRESS, TREASURY_ACCOUNT, createRawTransfer, extractFee } from "@moonwall/util";
+// These tests are checking the default value of FeesTreasuryProportion which is set to 20%
describeSuite({
id: "D011605",
title: "Fees - Transaction",
diff --git a/test/suites/dev/moonbase/test-parameters/test-parameters-runtime-FeesTreasuryProportion.ts b/test/suites/dev/moonbase/test-parameters/test-parameters-runtime-FeesTreasuryProportion.ts
index 7b9a5b017a..bc3b13b4ff 100644
--- a/test/suites/dev/moonbase/test-parameters/test-parameters-runtime-FeesTreasuryProportion.ts
+++ b/test/suites/dev/moonbase/test-parameters/test-parameters-runtime-FeesTreasuryProportion.ts
@@ -1,176 +1,223 @@
-import { describeSuite, expect, TransactionTypes } from "@moonwall/cli";
+import { describeSuite, expect, extractInfo, TransactionTypes } from "@moonwall/cli";
import {
alith,
+ ALITH_ADDRESS,
baltathar,
- BALTATHAR_ADDRESS,
+ BALTATHAR_PRIVATE_KEY,
+ CHARLETH_ADDRESS,
createRawTransfer,
extractFee,
Perbill,
TREASURY_ACCOUNT,
+ WEIGHT_PER_GAS,
} from "@moonwall/util";
import { parameterType, UNIT } from "./test-parameters";
import { BN } from "@polkadot/util";
+import { calculateFeePortions, ConstantStore, verifyLatestBlockFees } from "../../../../helpers";
+import { parseGwei } from "viem";
interface TestCase {
proportion: Perbill;
transfer_amount: bigint;
tipAmount: bigint;
+ priorityFeePerGas: bigint;
}
-// Recreation on fees.ration(burn_part, treasury_part)
-const split = (value: BN, part1: BN, part2: BN): [BN, BN] => {
- const total = part1.add(part2);
- if (total.eq(new BN(0)) || value.eq(new BN(0))) {
- return [new BN(0), new BN(0)];
- }
- const part1BN = value.mul(part1).div(total);
- const part2BN = value.sub(part1BN);
- return [part1BN, part2BN];
-};
-
describeSuite({
id: "DTemp03",
title: "Parameters - RuntimeConfig",
foundationMethods: "dev",
testCases: ({ it, context, log }) => {
let testCounter = 0;
+ const collatorAddress = ALITH_ADDRESS;
+ const senderPrivateKey = BALTATHAR_PRIVATE_KEY;
+ const senderKeyPair = baltathar;
+ const receiver = CHARLETH_ADDRESS;
const testCases: TestCase[] = [
{
proportion: new Perbill(0),
transfer_amount: 10n * UNIT,
tipAmount: 1n * UNIT,
+ priorityFeePerGas: parseGwei("1"),
},
{
proportion: new Perbill(1, 100),
transfer_amount: 1000n,
tipAmount: 100n,
+ priorityFeePerGas: 100n,
},
{
proportion: new Perbill(355, 1000),
transfer_amount: 5n * UNIT,
tipAmount: 111112n,
+ priorityFeePerGas: 111112n,
},
{
proportion: new Perbill(400, 1000),
transfer_amount: 10n * UNIT,
- tipAmount: 1n * UNIT,
+ tipAmount: 2n * UNIT,
+ priorityFeePerGas: parseGwei("2"),
},
{
proportion: new Perbill(500, 1000),
transfer_amount: 10n * UNIT,
tipAmount: 1n * UNIT,
+ priorityFeePerGas: parseGwei("1"),
},
{
proportion: new Perbill(963, 1000),
transfer_amount: 10n * UNIT,
tipAmount: 128n,
+ priorityFeePerGas: 128,
},
{
proportion: new Perbill(99, 100),
transfer_amount: 10n * UNIT,
tipAmount: 3n,
+ priorityFeePerGas: 3n,
},
{
proportion: new Perbill(1, 1),
transfer_amount: 10n * UNIT,
tipAmount: 32n,
+ priorityFeePerGas: 32n,
},
];
for (const t of testCases) {
- const burnProportion = new Perbill(new BN(1e9).sub(t.proportion.value()));
-
+ const treasuryPerbill = new BN(t.proportion.value());
const treasuryPercentage = t.proportion.value().toNumber() / 1e7;
- const burnPercentage = burnProportion.value().toNumber() / 1e7;
+ const burnPercentage = 100 - treasuryPercentage;
const calcTreasuryIncrease = (feeWithTip: bigint, tip?: bigint): bigint => {
const issuanceDecrease = calcIssuanceDecrease(feeWithTip, tip);
const treasuryIncrease = feeWithTip - issuanceDecrease;
return treasuryIncrease;
};
- const calcIssuanceDecrease = (feeWithTip: bigint, tip?: bigint): bigint => {
- const feeWithTipBN = new BN(feeWithTip.toString());
- const tipBN = new BN(tip?.toString() || "0");
- const feeWithoutTipBN = feeWithTipBN.sub(tipBN);
-
- const [burnFeePart, _treasuryFeePart] = split(
- feeWithoutTipBN,
- burnProportion.value(),
- t.proportion.value()
- );
- const [burnTipPart, _treasuryTipPart] = split(
- tipBN,
- burnProportion.value(),
- t.proportion.value()
- );
-
- return BigInt(burnFeePart.add(burnTipPart).toString());
+ const calcIssuanceDecrease = (feeWithTip: bigint, maybeTip?: bigint): bigint => {
+ const tip = maybeTip ?? 0n;
+ const feeWithoutTip = feeWithTip - tip;
+ const { burnt: feeBurnt } = calculateFeePortions(treasuryPerbill, feeWithoutTip);
+ const { burnt: tipBurnt } = calculateFeePortions(treasuryPerbill, tip);
+
+ return feeBurnt + tipBurnt;
};
for (const txnType of TransactionTypes) {
- it({
- id: `T${++testCounter}`,
- title:
- `Changing FeesTreasuryProportion to ${treasuryPercentage}% for Ethereum ` +
- `${txnType} transfers`,
- test: async () => {
- const param = parameterType(
- context,
- "RuntimeConfig",
- "FeesTreasuryProportion",
- t.proportion.value()
- );
- await context.createBlock(
- context
- .polkadotJs()
- .tx.sudo.sudo(context.polkadotJs().tx.parameters.setParameter(param.toU8a()))
- .signAsync(alith),
- { allowFailures: false }
- );
+ for (const withTip of txnType === "eip1559" ? [false, true] : [false]) {
+ testCounter++;
+ it({
+ id: `T${testCounter}x`,
+ title:
+ `Changing FeesTreasuryProportion to ${treasuryPercentage}% for Ethereum ` +
+ `${txnType} transfers with ${withTip ? "" : "no "}tip`,
+ test: async () => {
+ const { specVersion } = context.polkadotJs().consts.system.version;
+ const GENESIS_BASE_FEE = ConstantStore(context).GENESIS_BASE_FEE.get(
+ specVersion.toNumber()
+ );
+ const WEIGHT_FEE = ConstantStore(context).WEIGHT_FEE.get(specVersion.toNumber());
- const balBefore = await context.viem().getBalance({ address: TREASURY_ACCOUNT });
- const issuanceBefore = (
- await context.polkadotJs().query.balances.totalIssuance()
- ).toBigInt();
- const { result } = await context.createBlock(
- await createRawTransfer(context, BALTATHAR_ADDRESS, t.transfer_amount, {
- type: txnType,
- })
- );
+ const param = parameterType(
+ context,
+ "RuntimeConfig",
+ "FeesTreasuryProportion",
+ t.proportion.value()
+ );
+ await context.createBlock(
+ context
+ .polkadotJs()
+ .tx.sudo.sudo(context.polkadotJs().tx.parameters.setParameter(param.toU8a()))
+ .signAsync(alith),
+ { allowFailures: false }
+ );
- const balAfter = await context.viem().getBalance({ address: TREASURY_ACCOUNT });
- const issuanceAfter = (
- await context.polkadotJs().query.balances.totalIssuance()
- ).toBigInt();
+ const balBefore = await context.viem().getBalance({ address: TREASURY_ACCOUNT });
+ const collatorBalBefore = await context
+ .viem()
+ .getBalance({ address: collatorAddress });
+ const issuanceBefore = (
+ await context.polkadotJs().query.balances.totalIssuance()
+ ).toBigInt();
- const treasuryIncrease = balAfter - balBefore;
- const issuanceDecrease = issuanceBefore - issuanceAfter;
- const fee = extractFee(result?.events)!.amount.toBigInt();
+ const nextFeeMultiplier = (
+ await context.polkadotJs().query.transactionPayment.nextFeeMultiplier()
+ ).toBigInt();
+ const baseFee =
+ (nextFeeMultiplier * (WEIGHT_FEE * WEIGHT_PER_GAS)) / 1_000_000_000_000_000_000n;
- expect(
- treasuryIncrease + issuanceDecrease,
- `Sum of TreasuryIncrease and IssuanceDecrease should be equal to the fees`
- ).to.equal(fee);
+ const { result } = await context.createBlock(
+ await createRawTransfer(context, receiver, t.transfer_amount, {
+ privateKey: senderPrivateKey,
+ type: txnType,
+ maxFeePerGas: withTip ? GENESIS_BASE_FEE : undefined,
+ maxPriorityFeePerGas: withTip ? t.priorityFeePerGas : undefined,
+ })
+ );
- expect(
- treasuryIncrease,
- `${treasuryPercentage}% of the fees should go to treasury`
- ).to.equal(calcTreasuryIncrease(fee));
+ const receipt = await context
+ .viem("public")
+ .getTransactionReceipt({ hash: result!.hash as `0x${string}` });
- expect(issuanceDecrease, `${burnPercentage}% of the fees should be burned`).to.equal(
- calcIssuanceDecrease(fee)
- );
- },
- });
+ const balAfter = await context.viem().getBalance({ address: TREASURY_ACCOUNT });
+ const collatorBalAfter = await context
+ .viem()
+ .getBalance({ address: collatorAddress });
+ const issuanceAfter = (
+ await context.polkadotJs().query.balances.totalIssuance()
+ ).toBigInt();
+
+ const treasuryIncrease = balAfter - balBefore;
+ const issuanceDecrease = issuanceBefore - issuanceAfter;
+ const collatorIncrease = collatorBalAfter - collatorBalBefore;
+ const tipPaid = withTip
+ ? (() => {
+ let priorityPerGas = GENESIS_BASE_FEE - baseFee;
+ if (priorityPerGas > t.priorityFeePerGas) {
+ priorityPerGas = t.priorityFeePerGas;
+ }
+ return BigInt(priorityPerGas) * BigInt(receipt!.gasUsed);
+ })()
+ : 0n;
+ const fee = extractFee(result?.events)!.amount.toBigInt();
+ const feeWithoutTip = fee - tipPaid;
+
+ expect(
+ treasuryIncrease + issuanceDecrease,
+ `Sum of TreasuryIncrease and IssuanceDecrease should be equal to the fees without tip`
+ ).to.equal(feeWithoutTip);
+
+ expect(
+ treasuryIncrease,
+ `${treasuryPercentage}% of the fees should go to treasury`
+ ).to.equal(calcTreasuryIncrease(feeWithoutTip));
+
+ expect(issuanceDecrease, `${burnPercentage}% of the fees should be burned`).to.equal(
+ calcIssuanceDecrease(feeWithoutTip)
+ );
+
+ if (withTip) {
+ expect(collatorIncrease, "100% of the tip should go to the collator").to.equal(
+ tipPaid
+ );
+ } else {
+ expect(collatorIncrease, "No tip should be paid to the collator").to.equal(0n);
+ }
+
+ await verifyLatestBlockFees(context, t.transfer_amount);
+ },
+ });
+ }
}
for (const withTip of [false, true]) {
+ testCounter++;
it({
- id: `T${++testCounter}`,
+ id: `T${testCounter}x`,
title:
- `Changing FeesTreasuryProportion to ${treasuryPercentage}% for Substrate based` +
+ `Changing FeesTreasuryProportion to ${treasuryPercentage}% for Substrate based ` +
`transactions with ${withTip ? "" : "no "}tip`,
test: async () => {
const param = parameterType(
@@ -191,12 +238,13 @@ describeSuite({
const issuanceBefore = (
await context.polkadotJs().query.balances.totalIssuance()
).toBigInt();
+ const collatorBalBefore = await context.viem().getBalance({ address: collatorAddress });
const { result } = await context.createBlock(
context
.polkadotJs()
- .tx.balances.transferKeepAlive(alith.address, t.transfer_amount)
- .signAsync(baltathar, { tip: withTip ? t.tipAmount : 0n }),
+ .tx.balances.transferKeepAlive(receiver, t.transfer_amount)
+ .signAsync(senderKeyPair, { tip: withTip ? t.tipAmount : 0n }),
{ allowFailures: false }
);
@@ -204,24 +252,37 @@ describeSuite({
const issuanceAfter = (
await context.polkadotJs().query.balances.totalIssuance()
).toBigInt();
+ const collatorBalAfter = await context.viem().getBalance({ address: collatorAddress });
const treasuryIncrease = balanceAfter - balanceBefore;
const issuanceDecrease = issuanceBefore - issuanceAfter;
- const fee = extractFee(result?.events)!.amount.toBigInt();
+ const collatorIncrease = collatorBalAfter - collatorBalBefore;
+ const tipPaid = withTip ? t.tipAmount : 0n;
+ const feeWithoutTip = extractFee(result?.events)!.amount.toBigInt() - tipPaid;
expect(
treasuryIncrease + issuanceDecrease,
- `Sum of TreasuryIncrease and IssuanceDecrease should be equal to the fees`
- ).to.equal(fee);
+ `Sum of TreasuryIncrease and IssuanceDecrease should be equal to the fees without tip`
+ ).to.equal(feeWithoutTip);
expect(
treasuryIncrease,
`${treasuryPercentage}% of the fees should go to treasury`
- ).to.equal(calcTreasuryIncrease(fee, withTip ? t.tipAmount : undefined));
+ ).to.equal(calcTreasuryIncrease(feeWithoutTip));
expect(issuanceDecrease, `${burnPercentage}% of the fees should be burned`).to.equal(
- calcIssuanceDecrease(fee, withTip ? t.tipAmount : undefined)
+ calcIssuanceDecrease(feeWithoutTip)
);
+
+ if (withTip) {
+ expect(collatorIncrease, "100% of the tip should go to the collator").to.equal(
+ t.tipAmount
+ );
+ } else {
+ expect(collatorIncrease, "No tip should be paid to the collator").to.equal(0n);
+ }
+
+ await verifyLatestBlockFees(context, t.transfer_amount);
},
});
}
diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts
index db14721c5d..89fe34d91f 100644
--- a/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts
+++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts
@@ -1,6 +1,13 @@
import "@moonbeam-network/api-augment";
import { beforeAll, describeSuite, expect, fetchCompiledContract } from "@moonwall/cli";
-import { ALITH_ADDRESS, GLMR, PRECOMPILES, createViemTransaction } from "@moonwall/util";
+import {
+ ALITH_ADDRESS,
+ GLMR,
+ PRECOMPILES,
+ createViemTransaction,
+ CHARLETH_ADDRESS,
+ CHARLETH_PRIVATE_KEY,
+} from "@moonwall/util";
import {
verifyLatestBlockFees,
expectEVMResult,
@@ -214,7 +221,7 @@ describeSuite({
const fee_item = 0;
const weight = 100;
- const balBefore = await context.viem().getBalance({ address: ALITH_ADDRESS });
+ const balBefore = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
const { abi } = fetchCompiledContract("Xtokens");
const data = encodeFunctionData({
abi,
@@ -229,10 +236,11 @@ describeSuite({
txnType: "legacy",
gas: 500_000n,
gasPrice: BigInt(DEFAULT_TXN_MAX_BASE_FEE),
+ privateKey: CHARLETH_PRIVATE_KEY,
});
const { result } = await context.createBlock(rawTxn);
- const balAfter = await context.viem().getBalance({ address: ALITH_ADDRESS });
+ const balAfter = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
const receipt = await context
.viem()
.getTransactionReceipt({ hash: result!.hash as `0x${string}` });