diff --git a/node/service/src/chain_spec/moonbase.rs b/node/service/src/chain_spec/moonbase.rs index ef57970c4a..c6db37a070 100644 --- a/node/service/src/chain_spec/moonbase.rs +++ b/node/service/src/chain_spec/moonbase.rs @@ -30,9 +30,10 @@ use moonbase_runtime::{ EthereumChainIdConfig, EthereumConfig, GenesisAccount, GenesisConfig, InflationInfo, MaintenanceModeConfig, ParachainInfoConfig, ParachainStakingConfig, PolkadotXcmConfig, Precompiles, Range, SudoConfig, SystemConfig, TechCommitteeCollectiveConfig, - TreasuryCouncilCollectiveConfig, HOURS, WASM_BINARY, + TransactionPaymentConfig, TreasuryCouncilCollectiveConfig, HOURS, WASM_BINARY, }; use nimbus_primitives::NimbusId; +use pallet_transaction_payment::Multiplier; use sc_service::ChainType; #[cfg(test)] use sp_core::ecdsa; @@ -321,6 +322,9 @@ pub fn testnet_genesis( }, // This should initialize it to whatever we have set in the pallet polkadot_xcm: PolkadotXcmConfig::default(), + transaction_payment: TransactionPaymentConfig { + multiplier: Multiplier::from(8u128), + }, } } diff --git a/node/service/src/chain_spec/moonbeam.rs b/node/service/src/chain_spec/moonbeam.rs index 4b28804952..829fa0e8fb 100644 --- a/node/service/src/chain_spec/moonbeam.rs +++ b/node/service/src/chain_spec/moonbeam.rs @@ -30,10 +30,11 @@ use moonbeam_runtime::{ Balance, BalancesConfig, CouncilCollectiveConfig, CrowdloanRewardsConfig, DemocracyConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, GenesisAccount, GenesisConfig, InflationInfo, MaintenanceModeConfig, ParachainInfoConfig, ParachainStakingConfig, PolkadotXcmConfig, - Precompiles, Range, SystemConfig, TechCommitteeCollectiveConfig, + Precompiles, Range, SystemConfig, TechCommitteeCollectiveConfig, TransactionPaymentConfig, TreasuryCouncilCollectiveConfig, HOURS, WASM_BINARY, }; use nimbus_primitives::NimbusId; +use pallet_transaction_payment::Multiplier; use sc_service::ChainType; #[cfg(test)] use sp_core::ecdsa; @@ -313,6 +314,9 @@ pub fn testnet_genesis( }, // This should initialize it to whatever we have set in the pallet polkadot_xcm: PolkadotXcmConfig::default(), + transaction_payment: TransactionPaymentConfig { + multiplier: Multiplier::from(8u128), + }, } } diff --git a/node/service/src/chain_spec/moonriver.rs b/node/service/src/chain_spec/moonriver.rs index 250ccd5d8e..559d82cd0a 100644 --- a/node/service/src/chain_spec/moonriver.rs +++ b/node/service/src/chain_spec/moonriver.rs @@ -30,10 +30,11 @@ use moonriver_runtime::{ CouncilCollectiveConfig, CrowdloanRewardsConfig, DemocracyConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, GenesisAccount, GenesisConfig, InflationInfo, MaintenanceModeConfig, ParachainInfoConfig, ParachainStakingConfig, PolkadotXcmConfig, - Precompiles, Range, SystemConfig, TechCommitteeCollectiveConfig, + Precompiles, Range, SystemConfig, TechCommitteeCollectiveConfig, TransactionPaymentConfig, TreasuryCouncilCollectiveConfig, HOURS, WASM_BINARY, }; use nimbus_primitives::NimbusId; +use pallet_transaction_payment::Multiplier; use sc_service::ChainType; #[cfg(test)] use sp_core::ecdsa; @@ -313,6 +314,9 @@ pub fn testnet_genesis( }, // This should initialize it to whatever we have set in the pallet polkadot_xcm: PolkadotXcmConfig::default(), + transaction_payment: TransactionPaymentConfig { + multiplier: Multiplier::from(8u128), + }, } } diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index bf1f12e48f..2da03f60f3 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -196,7 +196,7 @@ pub fn native_version() -> NativeVersion { } const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -const NORMAL_WEIGHT: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_mul(3).saturating_div(4); +pub const NORMAL_WEIGHT: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_mul(3).saturating_div(4); // Here we assume Ethereum's base fee of 21000 gas and convert to weight, but we // subtract roughly the cost of a balance transfer from it (about 1/3 the cost) // and some cost to account for per-byte-fee. @@ -388,13 +388,11 @@ parameter_types! { pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); /// The adjustment variable of the runtime. Higher values will cause `TargetBlockFullness` to /// change the fees more rapidly. This low value causes changes to occur slowly over time. - pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(4, 1_000); /// Minimum amount of the multiplier. This value cannot be too low. A test case should ensure /// that combined with `AdjustmentVariable`, we can recover from the minimum. /// See `multiplier_can_grow_from_zero` in integration_tests.rs. - /// This value is currently only used by pallet-transaction-payment as an assertion that the - /// next multiplier is always > min value. - pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000); /// Maximum multiplier. We pick a value that is expensive but not impossibly so; it should act /// as a safety net. pub MaximumMultiplier: Multiplier = Multiplier::from(100_000u128); @@ -402,13 +400,25 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_ref_time(WEIGHT_PER_GAS); } -pub struct FixedGasPrice; -impl FeeCalculator for FixedGasPrice { +pub struct TransactionPaymentAsGasPrice; +impl FeeCalculator for TransactionPaymentAsGasPrice { fn min_gas_price() -> (U256, Weight) { - ( - (1 * currency::GIGAWEI * currency::SUPPLY_FACTOR).into(), - Weight::zero(), - ) + // TODO: transaction-payment differs from EIP-1559 in that its tip and length fees are not + // scaled by the multiplier, which means its multiplier will be overstated when + // applied to an ethereum transaction + // note: transaction-payment uses both a congestion modifier (next_fee_multiplier, which is + // updated once per block in on_finalize) and a 'WeightToFee' implementation. Our + // runtime implements this as a 'ConstantModifier', so we can get away with a simple + // multiplication here. + // It is imperative that `saturating_mul_int` be performed as late as possible in the + // expression since it involves fixed point multiplication with a division by a fixed + // divisor. This leads to truncation and subsequent precision loss if performed too early. + // This can lead to min_gas_price being same across blocks even if the multiplier changes. + // There's still some precision loss when the final `gas_price` (used_gas * min_gas_price) + // is computed in frontier, but that's currently unavoidable. + let min_gas_price = TransactionPayment::next_fee_multiplier() + .saturating_mul_int(currency::WEIGHT_FEE.saturating_mul(WEIGHT_PER_GAS as u128)); + (min_gas_price.into(), Weight::zero()) } } @@ -451,7 +461,7 @@ where moonbeam_runtime_common::impl_on_charge_evm_transaction!(); impl pallet_evm::Config for Runtime { - type FeeCalculator = FixedGasPrice; + type FeeCalculator = TransactionPaymentAsGasPrice; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; @@ -1250,7 +1260,7 @@ construct_runtime! { Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event} = 4, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 5, ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Storage, Inherent, Event} = 6, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 7, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Config, Event} = 7, ParachainInfo: parachain_info::{Pallet, Storage, Config} = 8, EthereumChainId: pallet_ethereum_chain_id::{Pallet, Storage, Config} = 9, EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 10, @@ -1508,7 +1518,6 @@ mod tests { 5_u8 ); assert_eq!(STORAGE_BYTE_FEE, Balance::from(100 * MICROUNIT)); - assert_eq!(FixedGasPrice::min_gas_price().0, (1 * GIGAWEI).into()); // democracy minimums assert_eq!( diff --git a/runtime/moonbase/tests/common/mod.rs b/runtime/moonbase/tests/common/mod.rs index be748b441c..4cfe12298a 100644 --- a/runtime/moonbase/tests/common/mod.rs +++ b/runtime/moonbase/tests/common/mod.rs @@ -27,9 +27,9 @@ use moonbase_runtime::{asset_config::AssetRegistrarMetadata, xcm_config::AssetTy pub use moonbase_runtime::{ currency::{GIGAWEI, SUPPLY_FACTOR, UNIT, WEI}, AccountId, AssetId, AssetManager, Assets, AuthorInherent, Balance, Balances, CrowdloanRewards, - Ethereum, Executive, FixedGasPrice, InflationInfo, LocalAssets, ParachainStaking, Range, - Runtime, RuntimeCall, RuntimeEvent, System, TransactionConverter, UncheckedExtrinsic, HOURS, - WEEKS, + Ethereum, Executive, InflationInfo, LocalAssets, ParachainStaking, Range, Runtime, RuntimeCall, + RuntimeEvent, System, TransactionConverter, TransactionPaymentAsGasPrice, UncheckedExtrinsic, + HOURS, WEEKS, }; use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID}; use sp_core::{Encode, H160}; @@ -38,12 +38,13 @@ use sp_runtime::{Digest, DigestItem, Perbill, Percent}; use std::collections::BTreeMap; use fp_rpc::ConvertTransaction; +use pallet_transaction_payment::Multiplier; // A valid signed Alice transfer. pub const VALID_ETH_TX: &str = - "f86880843b9aca0083b71b0094111111111111111111111111111111111111111182020080820a26a\ - 08c69faf613b9f72dbb029bb5d5acf42742d214c79743507e75fdc8adecdee928a001be4f58ff278ac\ - 61125a81a582a717d9c5d6554326c01b878297c6522b12282"; + "02f86d8205018085174876e80085e8d4a5100082520894f24ff3a9cf04c71dbc94d0b566f7a27b9456\ + 6cac8080c001a0e1094e1a52520a75c0255db96132076dd0f1263089f838bea548cbdbfc64a4d19f031c\ + 92a8cb04e2d68d20a6158d542a07ac440cc8d07b6e36af02db046d92df"; // An invalid signed Alice transfer with a gas limit artifically set to 0. pub const INVALID_ETH_TX: &str = @@ -291,6 +292,14 @@ impl ExtBuilder { ) .unwrap(); + >::assimilate_storage( + &pallet_transaction_payment::GenesisConfig { + multiplier: Multiplier::from(8u128), + }, + &mut t, + ) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); let local_assets = self.local_assets.clone(); diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index b5840aa545..a8b9f9d767 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -21,7 +21,7 @@ use common::*; use precompile_utils::{prelude::*, testing::*}; -use fp_evm::{Context, FeeCalculator}; +use fp_evm::Context; use frame_support::{ assert_noop, assert_ok, dispatch::{DispatchClass, Dispatchable}, @@ -66,10 +66,7 @@ use pallet_xcm_transactor::{Currency, CurrencyPayment, HrmpOperation, TransactWe use parity_scale_codec::Encode; use sha3::{Digest, Keccak256}; use sp_core::{crypto::UncheckedFrom, ByteArray, Pair, H160, U256}; -use sp_runtime::{ - traits::{Convert, One}, - DispatchError, ModuleError, TokenError, -}; +use sp_runtime::{traits::Convert, DispatchError, ModuleError, TokenError}; use xcm::latest::prelude::*; type AuthorMappingPCall = @@ -92,6 +89,9 @@ type XcmTransactorV1PCall = type XcmTransactorV2PCall = pallet_evm_precompile_xcm_transactor::v2::XcmTransactorPrecompileV2Call; +// TODO: can we construct a const U256...? +const BASE_FEE_GENISIS: u128 = 10 * GIGAWEI; + #[test] fn verify_randomness_precompile_gas_constants() { let weight_to_gas = |weight| { @@ -476,7 +476,6 @@ fn transfer_through_evm_to_stake() { assert_eq!(Balances::free_balance(AccountId::from(BOB)), 2_000 * UNIT); let gas_limit = 100000u64; - let gas_price: U256 = 1_000_000_000.into(); // Bob transfers 1000 UNIT to Charlie via EVM assert_ok!(RuntimeCall::EVM(pallet_evm::Call::::call { source: H160::from(BOB), @@ -484,7 +483,7 @@ fn transfer_through_evm_to_stake() { input: Vec::new(), value: (1_000 * UNIT).into(), gas_limit, - max_fee_per_gas: gas_price, + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: None, access_list: Vec::new(), @@ -881,7 +880,7 @@ fn claim_via_precompile() { // Alice uses the crowdloan precompile to claim through the EVM let gas_limit = 100000u64; - let gas_price: U256 = 1_000_000_000u64.into(); + let gas_price: U256 = BASE_FEE_GENISIS.into(); // Construct the call data (selector, amount) let mut call_data = Vec::::from([0u8; 4]); @@ -1127,7 +1126,7 @@ fn update_reward_address_via_precompile() { // Charlie uses the crowdloan precompile to update address through the EVM let gas_limit = 100000u64; - let gas_price: U256 = 1_000_000_000u64.into(); + let gas_price: U256 = BASE_FEE_GENISIS.into(); // Construct the input data to check if Bob is a contributor let mut call_data = Vec::::from([0u8; 36]); @@ -2113,33 +2112,6 @@ fn multiplier_can_grow_from_zero() { }) } -#[test] -#[ignore] // test runs for a very long time -fn multiplier_growth_simulator() { - use frame_support::traits::Get; - - // assume the multiplier is initially set to its minimum. We update it with values twice the - //target (target is 25%, thus 50%) and we see at which point it reaches 1. - let mut multiplier = moonbase_runtime::MinimumMultiplier::get(); - let block_weight = moonbase_runtime::TargetBlockFullness::get() - * RuntimeBlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap() - * 2; - let mut blocks = 0; - while multiplier <= Multiplier::one() { - run_with_system_weight(block_weight, || { - let next = moonbase_runtime::SlowAdjustingFeeUpdate::::convert(multiplier); - // ensure that it is growing as well. - assert!(next > multiplier, "{:?} !>= {:?}", next, multiplier); - multiplier = next; - }); - blocks += 1; - println!("block = {} multiplier {:?}", blocks, multiplier); - } -} - #[test] fn ethereum_invalid_transaction() { ExtBuilder::default().build().execute_with(|| { @@ -2176,13 +2148,28 @@ fn transfer_ed_0_substrate() { }); } +#[test] +fn initial_gas_fee_is_correct() { + use fp_evm::FeeCalculator; + + ExtBuilder::default().build().execute_with(|| { + let multiplier = TransactionPayment::next_fee_multiplier(); + assert_eq!(multiplier, Multiplier::from(8u128)); + + assert_eq!( + TransactionPaymentAsGasPrice::min_gas_price(), + (10_000_000_000u128.into(), Weight::zero()) + ); + }); +} + #[test] fn transfer_ed_0_evm() { ExtBuilder::default() .with_balances(vec![ ( AccountId::from(ALICE), - ((1 * UNIT) + (21_000 * 1_000_000_000)) + (1 * WEI), + ((1 * UNIT) + (21_000 * BASE_FEE_GENISIS)) + (1 * WEI), ), (AccountId::from(BOB), 0), ]) @@ -2195,7 +2182,7 @@ fn transfer_ed_0_evm() { input: Vec::new(), value: (1 * UNIT).into(), gas_limit: 21_000u64, - max_fee_per_gas: U256::from(1_000_000_000), + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: Some(U256::from(0)), access_list: Vec::new(), @@ -2212,7 +2199,7 @@ fn refund_ed_0_evm() { .with_balances(vec![ ( AccountId::from(ALICE), - ((1 * UNIT) + (21_777 * 1_000_000_000)), + ((1 * UNIT) + (21_777 * BASE_FEE_GENISIS)), ), (AccountId::from(BOB), 0), ]) @@ -2225,7 +2212,7 @@ fn refund_ed_0_evm() { input: Vec::new(), value: (1 * UNIT).into(), gas_limit: 21_777u64, - max_fee_per_gas: U256::from(1_000_000_000), + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: Some(U256::from(0)), access_list: Vec::new(), @@ -2234,7 +2221,7 @@ fn refund_ed_0_evm() { // ALICE is refunded assert_eq!( Balances::free_balance(AccountId::from(ALICE)), - 777 * 1_000_000_000, + 777 * BASE_FEE_GENISIS, ); }); } @@ -2278,7 +2265,7 @@ fn total_issuance_after_evm_transaction_with_priority_fee() { ExtBuilder::default() .with_balances(vec![( AccountId::from(BOB), - (1 * UNIT) + (21_000 * (2 * GIGAWEI)), + (1 * UNIT) + (21_000 * (2 * BASE_FEE_GENISIS)), )]) .build() .execute_with(|| { @@ -2290,16 +2277,16 @@ fn total_issuance_after_evm_transaction_with_priority_fee() { input: Vec::new(), value: (1 * UNIT).into(), gas_limit: 21_000u64, - max_fee_per_gas: U256::from(2 * GIGAWEI), - max_priority_fee_per_gas: Some(U256::from(1 * GIGAWEI)), + max_fee_per_gas: U256::from(2 * BASE_FEE_GENISIS), + max_priority_fee_per_gas: Some(U256::from(BASE_FEE_GENISIS)), nonce: Some(U256::from(0)), access_list: Vec::new(), }) .dispatch(::RuntimeOrigin::root())); let issuance_after = ::Currency::total_issuance(); - // Fee is 1 GWEI base fee + 1 GWEI tip. - let fee = ((2 * GIGAWEI) * 21_000) as f64; + // 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,); @@ -2311,10 +2298,11 @@ fn total_issuance_after_evm_transaction_with_priority_fee() { #[test] fn total_issuance_after_evm_transaction_without_priority_fee() { + use fp_evm::FeeCalculator; ExtBuilder::default() .with_balances(vec![( AccountId::from(BOB), - (1 * UNIT) + (21_000 * (2 * GIGAWEI)), + (1 * UNIT) + (21_000 * (2 * BASE_FEE_GENISIS)), )]) .build() .execute_with(|| { @@ -2326,7 +2314,7 @@ fn total_issuance_after_evm_transaction_without_priority_fee() { input: Vec::new(), value: (1 * UNIT).into(), gas_limit: 21_000u64, - max_fee_per_gas: U256::from(1 * GIGAWEI), + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: Some(U256::from(0)), access_list: Vec::new(), @@ -2335,7 +2323,9 @@ fn total_issuance_after_evm_transaction_without_priority_fee() { let issuance_after = ::Currency::total_issuance(); // Fee is 1 GWEI base fee. - let fee = ((1 * GIGAWEI) * 21_000) as f64; + 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,); @@ -3002,15 +2992,6 @@ fn precompile_existence() { }); } -#[test] -fn base_fee_should_default_to_associate_type_value() { - ExtBuilder::default().build().execute_with(|| { - let (base_fee, _) = - ::FeeCalculator::min_gas_price(); - assert_eq!(base_fee, (1 * GIGAWEI * SUPPLY_FACTOR).into()); - }); -} - #[test] fn substrate_based_fees_zero_txn_costs_only_base_extrinsic() { use frame_support::dispatch::{DispatchInfo, Pays}; @@ -3054,7 +3035,7 @@ fn evm_revert_substrate_events() { .into(), value: U256::zero(), // No value sent in EVM gas_limit: 500_000, - max_fee_per_gas: U256::from(1 * GIGAWEI), + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: Some(U256::from(0)), access_list: Vec::new(), @@ -3093,7 +3074,7 @@ fn evm_success_keeps_substrate_events() { .into(), value: U256::zero(), // No value sent in EVM gas_limit: 500_000, - max_fee_per_gas: U256::from(1 * GIGAWEI), + max_fee_per_gas: U256::from(BASE_FEE_GENISIS), max_priority_fee_per_gas: None, nonce: Some(U256::from(0)), access_list: Vec::new(), @@ -3111,3 +3092,219 @@ fn evm_success_keeps_substrate_events() { assert_eq!(transfer_count, 1, "there should be 1 transfer event"); }); } + +#[cfg(test)] +mod fee_tests { + use super::*; + use fp_evm::FeeCalculator; + use frame_support::{ + traits::{ConstU128, OnFinalize}, + weights::{ConstantMultiplier, WeightToFee}, + }; + use moonbase_runtime::{ + currency, BlockWeights, LengthToFee, MinimumMultiplier, SlowAdjustingFeeUpdate, + TargetBlockFullness, NORMAL_WEIGHT, WEIGHT_PER_GAS, + }; + use sp_runtime::{FixedPointNumber, Perbill}; + + fn run_with_system_weight(w: Weight, mut assertions: F) + where + F: FnMut() -> (), + { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + System::set_block_consumed_resources(w, 0); + assertions() + }); + } + + #[test] + fn test_multiplier_can_grow_from_zero() { + let minimum_multiplier = MinimumMultiplier::get(); + let target = TargetBlockFullness::get() + * BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap(); + // if the min is too small, then this will not change, and we are doomed forever. + // the weight is 1/100th bigger than target. + run_with_system_weight(target * 101 / 100, || { + let next = SlowAdjustingFeeUpdate::::convert(minimum_multiplier); + assert!( + next > minimum_multiplier, + "{:?} !>= {:?}", + next, + minimum_multiplier + ); + }) + } + + #[test] + fn test_fee_calculation() { + let base_extrinsic = BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let multiplier = sp_runtime::FixedU128::from_float(0.999000000000000000); + let extrinsic_len = 100u32; + let extrinsic_weight = 5_000u64; + let tip = 42u128; + type WeightToFeeImpl = + ConstantMultiplier>; + type LengthToFeeImpl = LengthToFee; + + // base_fee + (multiplier * extrinsic_weight_fee) + extrinsic_length_fee + tip + let expected_fee = WeightToFeeImpl::weight_to_fee(&base_extrinsic) + + multiplier.saturating_mul_int(WeightToFeeImpl::weight_to_fee( + &Weight::from_ref_time(extrinsic_weight), + )) + LengthToFeeImpl::weight_to_fee(&Weight::from_ref_time( + extrinsic_len as u64, + )) + tip; + + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier); + let actual_fee = TransactionPayment::compute_fee( + extrinsic_len, + &frame_support::dispatch::DispatchInfo { + class: DispatchClass::Normal, + pays_fee: frame_support::dispatch::Pays::Yes, + weight: Weight::from_ref_time(extrinsic_weight), + }, + tip, + ); + + assert_eq!( + expected_fee, + actual_fee, + "The actual fee did not match the expected fee, diff {}", + actual_fee - expected_fee + ); + }); + } + + #[test] + fn test_min_gas_price_is_deterministic() { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + let multiplier = sp_runtime::FixedU128::from_u32(1); + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier); + let actual = TransactionPaymentAsGasPrice::min_gas_price().0; + let expected: U256 = multiplier + .saturating_mul_int(currency::WEIGHT_FEE.saturating_mul(WEIGHT_PER_GAS as u128)) + .into(); + + assert_eq!(expected, actual); + }); + } + + #[test] + fn test_min_gas_price_has_no_precision_loss_from_saturating_mul_int() { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + let multiplier_1 = sp_runtime::FixedU128::from_float(0.999593900000000000); + let multiplier_2 = sp_runtime::FixedU128::from_float(0.999593200000000000); + + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier_1); + let a = TransactionPaymentAsGasPrice::min_gas_price(); + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier_2); + let b = TransactionPaymentAsGasPrice::min_gas_price(); + + assert_ne!( + a, b, + "both gas prices were equal, unexpected precision loss incurred" + ); + }); + } + + #[test] + fn test_fee_scenarios() { + use sp_runtime::FixedU128; + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + let weight_fee_per_gas = currency::WEIGHT_FEE.saturating_mul(WEIGHT_PER_GAS as u128); + let sim = |start_gas_price: u128, fullness: Perbill, num_blocks: u64| -> U256 { + let start_multiplier = + FixedU128::from_rational(start_gas_price, weight_fee_per_gas); + pallet_transaction_payment::NextFeeMultiplier::::set(start_multiplier); + + let block_weight = NORMAL_WEIGHT * fullness; + + for i in 0..num_blocks { + System::set_block_number(i as u32); + System::set_block_consumed_resources(block_weight, 0); + TransactionPayment::on_finalize(i as u32); + } + + TransactionPaymentAsGasPrice::min_gas_price().0 + }; + + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(0), 1), + U256::from(999_000_500), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(25), 1), + U256::from(1_000_000_000), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(50), 1), + U256::from(1_001_000_500), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(100), 1), + U256::from(1_003_004_500), + ); + + // 1 "real" hour (at 12-second blocks) + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(0), 300), + U256::from(740_818_257), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(25), 300), + U256::from(1_000_000_000), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(50), 300), + U256::from(1_349_858_740), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(100), 300), + U256::from(2_459_599_798u128), + ); + + // 1 "real" day (at 12-second blocks) + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(0), 7200), + U256::from(1_250_000), // lower bound enforced + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(25), 7200), + U256::from(1_000_000_000), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(50), 7200), + U256::from(1_339_429_158_283u128), + ); + assert_eq!( + sim(1_000_000_000, Perbill::from_percent(100), 7200), + U256::from(125_000_000_000_000u128), // upper bound enforced + ); + }); + } +} diff --git a/runtime/moonbase/tests/runtime_apis.rs b/runtime/moonbase/tests/runtime_apis.rs index 0a6613f344..9145e47260 100644 --- a/runtime/moonbase/tests/runtime_apis.rs +++ b/runtime/moonbase/tests/runtime_apis.rs @@ -54,7 +54,10 @@ fn ethereum_runtime_rpc_api_account_basic() { #[test] fn ethereum_runtime_rpc_api_gas_price() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(Runtime::gas_price(), FixedGasPrice::min_gas_price().0); + assert_eq!( + Runtime::gas_price(), + TransactionPaymentAsGasPrice::min_gas_price().0 + ); }); } @@ -186,7 +189,7 @@ fn ethereum_runtime_rpc_api_create() { #[test] fn ethereum_runtime_rpc_api_current_transaction_statuses() { let alith = ::AddressMapping::into_account_id( - H160::from_str("6be02d1d3665660d22ff9624b7be0551ee1ac91b") + H160::from_str("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac") .expect("internal H160 is valid; qed"), ); ExtBuilder::default() @@ -245,7 +248,7 @@ fn ethereum_runtime_rpc_api_current_block() { #[test] fn ethereum_runtime_rpc_api_current_receipts() { let alith = ::AddressMapping::into_account_id( - H160::from_str("6be02d1d3665660d22ff9624b7be0551ee1ac91b") + H160::from_str("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac") .expect("internal H160 is valid; qed"), ); ExtBuilder::default() diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index a5e3db5594..9cdfe33698 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -1210,7 +1210,7 @@ construct_runtime! { // Monetary stuff. Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Config, Event} = 11, // Consensus support. ParachainStaking: pallet_parachain_staking::{Pallet, Call, Storage, Event, Config} = 20, @@ -1481,7 +1481,6 @@ mod tests { 5_u8 ); assert_eq!(STORAGE_BYTE_FEE, Balance::from(10 * MILLIGLMR)); - assert_eq!(FixedGasPrice::min_gas_price().0, (100 * GIGAWEI).into()); // democracy minimums assert_eq!( diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index fdfdbf0103..8ddbd9e092 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -46,17 +46,13 @@ use pallet_evm::PrecompileSet; use pallet_evm_precompileset_assets_erc20::{ AccountIdAssetIdConversion, IsLocal, SELECTOR_LOG_APPROVAL, SELECTOR_LOG_TRANSFER, }; -use pallet_transaction_payment::Multiplier; use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; use parity_scale_codec::Encode; use polkadot_parachain::primitives::Sibling; use precompile_utils::{prelude::*, testing::*}; use sha3::{Digest, Keccak256}; use sp_core::{ByteArray, Pair, H160, U256}; -use sp_runtime::{ - traits::{Convert, One}, - DispatchError, ModuleError, TokenError, -}; +use sp_runtime::{traits::Convert, DispatchError, ModuleError, TokenError}; use std::str::from_utf8; use xcm::latest::prelude::*; use xcm::{VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation}; @@ -1210,33 +1206,6 @@ fn multiplier_can_grow_from_zero() { }) } -#[test] -#[ignore] // test runs for a very long time -fn multiplier_growth_simulator() { - use frame_support::traits::Get; - - // assume the multiplier is initially set to its minimum. We update it with values twice the - //target (target is 25%, thus 50%) and we see at which point it reaches 1. - let mut multiplier = moonbeam_runtime::MinimumMultiplier::get(); - let block_weight = moonbeam_runtime::TargetBlockFullness::get() - * RuntimeBlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap() - * 2; - let mut blocks = 0; - while multiplier <= Multiplier::one() { - run_with_system_weight(block_weight, || { - let next = moonbeam_runtime::SlowAdjustingFeeUpdate::::convert(multiplier); - // ensure that it is growing as well. - assert!(next > multiplier, "{:?} !>= {:?}", next, multiplier); - multiplier = next; - }); - blocks += 1; - println!("block = {} multiplier {:?}", blocks, multiplier); - } -} - #[test] fn ethereum_invalid_transaction() { ExtBuilder::default().build().execute_with(|| { @@ -3000,3 +2969,98 @@ fn evm_success_keeps_substrate_events() { assert_eq!(transfer_count, 1, "there should be 1 transfer event"); }); } + +#[cfg(test)] +mod fee_tests { + use super::*; + use frame_support::{ + traits::ConstU128, + weights::{ConstantMultiplier, WeightToFee}, + }; + use moonbeam_runtime::{ + currency, LengthToFee, MinimumMultiplier, RuntimeBlockWeights, SlowAdjustingFeeUpdate, + TargetBlockFullness, TransactionPayment, + }; + use sp_core::Get; + use sp_runtime::FixedPointNumber; + + fn run_with_system_weight(w: Weight, mut assertions: F) + where + F: FnMut() -> (), + { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + System::set_block_consumed_resources(w, 0); + assertions() + }); + } + + #[test] + fn test_multiplier_can_grow_from_zero() { + let minimum_multiplier = MinimumMultiplier::get(); + let target = TargetBlockFullness::get() + * RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap(); + // if the min is too small, then this will not change, and we are doomed forever. + // the weight is 1/100th bigger than target. + run_with_system_weight(target * 101 / 100, || { + let next = SlowAdjustingFeeUpdate::::convert(minimum_multiplier); + assert!( + next > minimum_multiplier, + "{:?} !>= {:?}", + next, + minimum_multiplier + ); + }) + } + + #[test] + fn test_fee_calculation() { + let base_extrinsic = RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let multiplier = sp_runtime::FixedU128::from_float(0.999000000000000000); + let extrinsic_len = 100u32; + let extrinsic_weight = 5_000u64; + let tip = 42u128; + type WeightToFeeImpl = ConstantMultiplier>; + type LengthToFeeImpl = LengthToFee; + + // base_fee + (multiplier * extrinsic_weight_fee) + extrinsic_length_fee + tip + let expected_fee = WeightToFeeImpl::weight_to_fee(&base_extrinsic) + + multiplier.saturating_mul_int(WeightToFeeImpl::weight_to_fee( + &Weight::from_ref_time(extrinsic_weight), + )) + LengthToFeeImpl::weight_to_fee(&Weight::from_ref_time( + extrinsic_len as u64, + )) + tip; + + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier); + let actual_fee = TransactionPayment::compute_fee( + extrinsic_len, + &frame_support::dispatch::DispatchInfo { + class: DispatchClass::Normal, + pays_fee: frame_support::dispatch::Pays::Yes, + weight: Weight::from_ref_time(extrinsic_weight), + }, + tip, + ); + + assert_eq!( + expected_fee, + actual_fee, + "The actual fee did not match the expected fee, diff {}", + actual_fee - expected_fee + ); + }); + } +} diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index a0fd6074d9..b9f54ff89d 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -1122,7 +1122,7 @@ construct_runtime! { // Monetary stuff. Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Config, Event} = 11, // Consensus support. ParachainStaking: pallet_parachain_staking::{Pallet, Call, Storage, Event, Config} = 20, @@ -1396,7 +1396,6 @@ mod tests { 5_u8 ); assert_eq!(STORAGE_BYTE_FEE, Balance::from(100 * MICROMOVR)); - assert_eq!(FixedGasPrice::min_gas_price().0, (1 * GIGAWEI).into()); // democracy minimums assert_eq!( diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index 05f2172c06..cb53cc6e11 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -43,17 +43,13 @@ use pallet_evm::PrecompileSet; use pallet_evm_precompileset_assets_erc20::{ AccountIdAssetIdConversion, IsLocal, SELECTOR_LOG_APPROVAL, SELECTOR_LOG_TRANSFER, }; -use pallet_transaction_payment::Multiplier; use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; use parity_scale_codec::Encode; use polkadot_parachain::primitives::Sibling; use precompile_utils::{prelude::*, testing::*}; use sha3::{Digest, Keccak256}; use sp_core::{ByteArray, Pair, H160, U256}; -use sp_runtime::{ - traits::{Convert, One}, - DispatchError, ModuleError, TokenError, -}; +use sp_runtime::{traits::Convert, DispatchError, ModuleError, TokenError}; use std::str::from_utf8; use xcm::latest::prelude::*; use xcm::{VersionedMultiAssets, VersionedMultiLocation}; @@ -1189,33 +1185,6 @@ fn multiplier_can_grow_from_zero() { }) } -#[test] -#[ignore] // test runs for a very long time -fn multiplier_growth_simulator() { - use frame_support::traits::Get; - - // assume the multiplier is initially set to its minimum. We update it with values twice the - //target (target is 25%, thus 50%) and we see at which point it reaches 1. - let mut multiplier = moonriver_runtime::MinimumMultiplier::get(); - let block_weight = moonriver_runtime::TargetBlockFullness::get() - * RuntimeBlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap() - * 2; - let mut blocks = 0; - while multiplier <= Multiplier::one() { - run_with_system_weight(block_weight, || { - let next = moonriver_runtime::SlowAdjustingFeeUpdate::::convert(multiplier); - // ensure that it is growing as well. - assert!(next > multiplier, "{:?} !>= {:?}", next, multiplier); - multiplier = next; - }); - blocks += 1; - println!("block = {} multiplier {:?}", blocks, multiplier); - } -} - #[test] fn ethereum_invalid_transaction() { ExtBuilder::default().build().execute_with(|| { @@ -2913,3 +2882,96 @@ fn evm_success_keeps_substrate_events() { assert_eq!(transfer_count, 1, "there should be 1 transfer event"); }); } + +#[cfg(test)] +mod fee_tests { + use super::*; + use frame_support::{ + traits::ConstU128, + weights::{ConstantMultiplier, WeightToFee}, + }; + use moonriver_runtime::{ + currency, LengthToFee, MinimumMultiplier, RuntimeBlockWeights, SlowAdjustingFeeUpdate, + TargetBlockFullness, TransactionPayment, + }; + use sp_core::Get; + use sp_runtime::FixedPointNumber; + + fn run_with_system_weight(w: Weight, mut assertions: F) + where + F: FnMut() -> (), + { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + System::set_block_consumed_resources(w, 0); + assertions() + }); + } + + #[test] + fn test_multiplier_can_grow_from_zero() { + let minimum_multiplier = MinimumMultiplier::get(); + let target = TargetBlockFullness::get() + * RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap(); + // if the min is too small, then this will not change, and we are doomed forever. + // the weight is 1/100th bigger than target. + run_with_system_weight(target * 101 / 100, || { + let next = SlowAdjustingFeeUpdate::::convert(minimum_multiplier); + assert!( + next > minimum_multiplier, + "{:?} !>= {:?}", + next, + minimum_multiplier + ); + }) + } + + #[test] + fn test_fee_calculation() { + let base_extrinsic = RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let multiplier = sp_runtime::FixedU128::from_float(0.999000000000000000); + let extrinsic_len = 100u32; + let extrinsic_weight = Weight::from_ref_time(5_000u64); + let tip = 42u128; + type WeightToFeeImpl = ConstantMultiplier>; + type LengthToFeeImpl = LengthToFee; + + // base_fee + (multiplier * extrinsic_weight_fee) + extrinsic_length_fee + tip + let expected_fee = WeightToFeeImpl::weight_to_fee(&base_extrinsic) + + multiplier.saturating_mul_int(WeightToFeeImpl::weight_to_fee(&extrinsic_weight)) + + LengthToFeeImpl::weight_to_fee(&(Weight::from_ref_time(extrinsic_len as u64))) + + tip; + + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::::set(multiplier); + let actual_fee = TransactionPayment::compute_fee( + extrinsic_len, + &frame_support::dispatch::DispatchInfo { + class: DispatchClass::Normal, + pays_fee: frame_support::dispatch::Pays::Yes, + weight: extrinsic_weight, + }, + tip, + ); + + assert_eq!( + expected_fee, + actual_fee, + "The actual fee did not match the expected fee, diff {}", + actual_fee - expected_fee + ); + }); + } +} diff --git a/tests/tests/test-balance/test-balance-existential.ts b/tests/tests/test-balance/test-balance-existential.ts index 097b4e7806..b69cd41787 100644 --- a/tests/tests/test-balance/test-balance-existential.ts +++ b/tests/tests/test-balance/test-balance-existential.ts @@ -10,7 +10,7 @@ import { createTransfer } from "../../util/transactions"; describeDevMoonbeamAllEthTxTypes("Existential Deposit", (context) => { let randomWeb3Account: Account; - it("setup accounts", async function () { + before("setup accounts", async function () { randomWeb3Account = context.web3.eth.accounts.create("random"); const { result, block } = await context.createBlock( createTransfer(context, randomWeb3Account.address, 10n * GLMR, { @@ -22,8 +22,12 @@ describeDevMoonbeamAllEthTxTypes("Existential Deposit", (context) => { }); it("should be disabled (no reaped account on 0 balance)", async function () { + const txFeePerGas = + context.ethTransactionType == "EIP1559" + ? BigInt(await context.web3.eth.getGasPrice()) + : MIN_GAS_PRICE; const { block, result } = await context.createBlock( - createTransfer(context, alith.address, 10n * GLMR - 21000n * MIN_GAS_PRICE, { + createTransfer(context, alith.address, 10n * GLMR - 21000n * txFeePerGas, { from: randomWeb3Account.address, privateKey: randomWeb3Account.privateKey, gas: 21000, @@ -35,9 +39,11 @@ describeDevMoonbeamAllEthTxTypes("Existential Deposit", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Existential Deposit", (context) => { +// run in legacy only -- this test requires that exactly its gas_price * gas_limit be deducted from +// the sender's account +describeDevMoonbeam("Existential Deposit", (context) => { let randomWeb3Account: Account; - it("setup accounts", async function () { + before("setup accounts", async function () { randomWeb3Account = context.web3.eth.accounts.create("random"); await context.createBlock( createTransfer(context, randomWeb3Account.address, 10n * GLMR, { @@ -49,7 +55,7 @@ describeDevMoonbeamAllEthTxTypes("Existential Deposit", (context) => { it("should be disabled (no reaped account on tiny balance - 1)", async function () { await context.createBlock( - createTransfer(context, baltathar.address, 10n * GLMR - 1n - 21000n * 1_000_000_000n, { + createTransfer(context, baltathar.address, 10n * GLMR - 1n - 21000n * MIN_GAS_PRICE, { from: randomWeb3Account.address, privateKey: randomWeb3Account.privateKey, gas: 21000, diff --git a/tests/tests/test-balance/test-balance-transfer.ts b/tests/tests/test-balance/test-balance-transfer.ts index 0fdfe4f76b..e09f59291f 100644 --- a/tests/tests/test-balance/test-balance-transfer.ts +++ b/tests/tests/test-balance/test-balance-transfer.ts @@ -19,18 +19,18 @@ import { createTransfer, } from "../../util/transactions"; -describeDevMoonbeamAllEthTxTypes("Balance transfer cost", (context) => { +describeDevMoonbeam("Balance transfer cost", (context) => { const randomAccount = generateKeyringPair(); - it("should cost 21000 * 1_000_000_000", async function () { + it("should cost 21000 * 10_000_000_000", async function () { await context.createBlock(createTransfer(context, randomAccount.address, 0)); expect(await context.web3.eth.getBalance(alith.address, 1)).to.equal( - (ALITH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 1_000_000_000n).toString() + (ALITH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 10_000_000_000n).toString() ); }); }); -describeDevMoonbeamAllEthTxTypes("Balance transfer", (context) => { +describeDevMoonbeam("Balance transfer", (context) => { const randomAccount = generateKeyringPair(); before("Create block with transfer to test account of 512", async () => { await context.createBlock(); @@ -38,7 +38,7 @@ describeDevMoonbeamAllEthTxTypes("Balance transfer", (context) => { await createTransfer(context, randomAccount.address, 512), ]); expect(await context.web3.eth.getBalance(alith.address, "pending")).to.equal( - (ALITH_GENESIS_TRANSFERABLE_BALANCE - 512n - 21000n * 1_000_000_000n).toString() + (ALITH_GENESIS_TRANSFERABLE_BALANCE - 512n - 21000n * 10_000_000_000n).toString() ); expect(await context.web3.eth.getBalance(randomAccount.address, "pending")).to.equal("512"); await context.createBlock(); @@ -47,7 +47,7 @@ describeDevMoonbeamAllEthTxTypes("Balance transfer", (context) => { it("should decrease from account", async function () { // 21000 covers the cost of the transaction expect(await context.web3.eth.getBalance(alith.address, 2)).to.equal( - (ALITH_GENESIS_TRANSFERABLE_BALANCE - 512n - 21000n * 1_000_000_000n).toString() + (ALITH_GENESIS_TRANSFERABLE_BALANCE - 512n - 21000n * 10_000_000_000n).toString() ); }); @@ -68,7 +68,7 @@ describeDevMoonbeamAllEthTxTypes("Balance transfer", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Balance transfer - fees", (context) => { +describeDevMoonbeam("Balance transfer - fees", (context) => { const randomAccount = generateKeyringPair(); before("Create block with transfer to test account of 512", async () => { await context.createBlock(createTransfer(context, randomAccount.address, 512)); @@ -98,7 +98,7 @@ describeDevMoonbeam( const preBalance = BigInt(await context.web3.eth.getBalance(alith.address)); // With this configuration no priority fee will be used, as the max_fee_per_gas is exactly the // base fee. Expect the balances to reflect this case. - const maxFeePerGas = 1_000_000_000; + const maxFeePerGas = 10_000_000_000; await context.createBlock( createTransaction(context, { @@ -128,7 +128,7 @@ describeDevMoonbeam( const preBalance = BigInt(await context.web3.eth.getBalance(alith.address)); // With this configuration only half of the priority fee will be used, as the max_fee_per_gas // is 2GWEI and the base fee is 1GWEI. - const maxFeePerGas = 1_000_000_000 * 2; + const maxFeePerGas = 10_000_000_000 * 2; await context.createBlock( createTransaction(context, { diff --git a/tests/tests/test-contract/test-contract-evm-limits.ts b/tests/tests/test-contract/test-contract-evm-limits.ts index 38f31b65a8..c26afa5288 100644 --- a/tests/tests/test-contract/test-contract-evm-limits.ts +++ b/tests/tests/test-contract/test-contract-evm-limits.ts @@ -32,7 +32,7 @@ describeDevMoonbeam("Contract - Excessive memory allocation", (context) => { to: null, value: value, gas: "0x100000", - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, data: "0x4141046159864141414141343933343346460100000028F900E06F01000000F71E01000000000000", }, ALITH_PRIVATE_KEY diff --git a/tests/tests/test-eth-fee/test-eth-fee-history.ts b/tests/tests/test-eth-fee/test-eth-fee-history.ts index 80b8a699cb..071413ff12 100644 --- a/tests/tests/test-eth-fee/test-eth-fee-history.ts +++ b/tests/tests/test-eth-fee/test-eth-fee-history.ts @@ -8,6 +8,7 @@ import { alith, ALITH_PRIVATE_KEY } from "../../util/accounts"; import { getCompiled } from "../../util/contracts"; import { customWeb3Request, web3Subscribe } from "../../util/providers"; import { describeDevMoonbeamAllEthTxTypes, DevTestContext } from "../../util/setup-dev-tests"; +import { DEFAULT_TXN_MAX_BASE_FEE } from "../../util/transactions"; // We use ethers library in this test as apparently web3js's types are not fully EIP-1559 // compliant yet. @@ -61,7 +62,7 @@ describeDevMoonbeamAllEthTxTypes("Fee History", (context) => { } it("result length should match spec", async function () { - let max_fee_per_gas = "0x3B9ACA00"; + let max_fee_per_gas = "0x" + DEFAULT_TXN_MAX_BASE_FEE.toString(16); let block_count = 2; let reward_percentiles = [20, 50, 70]; let priority_fees = [1, 2, 3]; @@ -98,7 +99,7 @@ describeDevMoonbeamAllEthTxTypes("Fee History", (context) => { }); it("should calculate percentiles", async function () { - let max_fee_per_gas = "0x3B9ACA00"; + let max_fee_per_gas = "0x" + DEFAULT_TXN_MAX_BASE_FEE.toString(16); let block_count = 11; let reward_percentiles = [20, 50, 70, 85, 100]; let priority_fees = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; diff --git a/tests/tests/test-eth-fee/test-eth-txn-weights.ts b/tests/tests/test-eth-fee/test-eth-txn-weights.ts index a19e5fa3cd..e99255fb83 100644 --- a/tests/tests/test-eth-fee/test-eth-txn-weights.ts +++ b/tests/tests/test-eth-fee/test-eth-txn-weights.ts @@ -28,7 +28,7 @@ describeDevMoonbeam("Ethereum Weight Accounting", (context) => { createTransaction(context, { ...ALITH_TRANSACTION_TEMPLATE, gas: EXTRINSIC_GAS_LIMIT, - maxFeePerGas: 1_000_000_000, + maxFeePerGas: 10_000_000_000, maxPriorityFeePerGas: 0, to: baltathar.address, nonce: 0, diff --git a/tests/tests/test-eth-pool/test-eth-pool-error.ts b/tests/tests/test-eth-pool/test-eth-pool-error.ts index b83f889718..c7978d0ea5 100644 --- a/tests/tests/test-eth-pool/test-eth-pool-error.ts +++ b/tests/tests/test-eth-pool/test-eth-pool-error.ts @@ -21,11 +21,11 @@ describeDevMoonbeam("Ethereum Rpc pool errors - replacement transaction underpri it("replacement transaction underpriced", async function () { const tx_1 = await createTransfer(context, baltathar.address, 1, { nonce: 0, - gasPrice: 2_000_000_000, + gasPrice: 20_000_000_000_000, }); const tx_2 = await createTransfer(context, baltathar.address, 1, { nonce: 0, - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000_000, }); await customWeb3Request(context.web3, "eth_sendRawTransaction", [tx_1]); const res_a2 = await customWeb3Request(context.web3, "eth_sendRawTransaction", [tx_2]); @@ -96,7 +96,7 @@ describeDevMoonbeam( "Ethereum Rpc pool errors - insufficient funds for gas * price + value", (context) => { it("insufficient funds for gas * price + value", async function () { - const amount = ALITH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 1_000_000_000n + 1n; + const amount = ALITH_GENESIS_TRANSFERABLE_BALANCE - 21000n * 10_000_000_000n + 1n; const tx = await createTransfer(context, baltathar.address, amount, { nonce: 0 }); const res = await customWeb3Request(context.web3, "eth_sendRawTransaction", [tx]); expect(res.error).to.include({ @@ -112,7 +112,8 @@ describeDevMoonbeam( it("max priority fee per gas higher than max fee per gast", async function () { const tx = await createTransfer(context, baltathar.address, 1, { nonce: 0, - maxPriorityFeePerGas: 2_000_000_000, + maxFeePerGas: 100_000_000_000, + maxPriorityFeePerGas: 200_000_000_000, }); const res = await customWeb3Request(context.web3, "eth_sendRawTransaction", [tx]); expect(res.error).to.include({ diff --git a/tests/tests/test-eth-tx/test-eth-tx-size.ts b/tests/tests/test-eth-tx/test-eth-tx-size.ts index 9606b2dc66..9a9faf2496 100644 --- a/tests/tests/test-eth-tx/test-eth-tx-size.ts +++ b/tests/tests/test-eth-tx/test-eth-tx-size.ts @@ -20,7 +20,7 @@ describeDevMoonbeam("Ethereum Transaction - Large Transaction", (context) => { to: null, value: "0x0", gasLimit: EXTRINSIC_GAS_LIMIT, - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, data: data, nonce: await context.web3.eth.getTransactionCount(alith.address), }); diff --git a/tests/tests/test-eth-tx/test-eth-tx-types.ts b/tests/tests/test-eth-tx/test-eth-tx-types.ts index b8fa91000c..3aac0e0b29 100644 --- a/tests/tests/test-eth-tx/test-eth-tx-types.ts +++ b/tests/tests/test-eth-tx/test-eth-tx-types.ts @@ -4,7 +4,11 @@ import { expect } from "chai"; import { ALITH_PRIVATE_KEY, baltathar } from "../../util/accounts"; import { describeDevMoonbeam } from "../../util/setup-dev-tests"; -import { createTransaction, createTransfer } from "../../util/transactions"; +import { + createTransaction, + createTransfer, + DEFAULT_TXN_MAX_BASE_FEE, +} from "../../util/transactions"; describeDevMoonbeam( "Ethereum Transaction - Legacy", @@ -15,7 +19,7 @@ describeDevMoonbeam( privateKey: ALITH_PRIVATE_KEY, to: baltathar.address, gas: 12_000_000, - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, value: 512, }) ); @@ -26,15 +30,15 @@ describeDevMoonbeam( expect(extrinsic.isLegacy).to.be.true; expect(extrinsic.asLegacy.toJSON()).to.deep.equal({ nonce: 0, - gasPrice: 1000000000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, gasLimit: 12000000, action: { call: baltathar.address.toLowerCase() }, value: 512, input: "0x", signature: { - v: 2597, - r: "0x440c713c1ea8ced9edacac8a33baa89411dca31af33b5c6e2c8e4a3c95112ab4", - s: "0x17c303f32862b65034da593cc0fb1178c915ef7a0b9c221ff3b7d00647b208fb", + v: 2598, + r: "0xc4d57ab7b0e601a95299b70a46fdbb16371b477669e0f8245e0a9f12e27e15f2", + s: "0x4186b0a32dd279fed20b1f20805845e24eff2a2a035801fe19419c09e861a62d", }, }); }); @@ -62,7 +66,7 @@ describeDevMoonbeam( expect(extrinsic.asEip2930.toJSON()).to.deep.equal({ chainId: 1281, nonce: 0, - gasPrice: 1000000000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, gasLimit: 21000, action: { call: baltathar.address.toLowerCase(), @@ -70,9 +74,9 @@ describeDevMoonbeam( value: 512, input: "0x", accessList: [], - oddYParity: false, - r: "0x6d61b9498e1ddcfa490ef3cb45d0152ad328f7f61d69e61d901a21eab86814c0", - s: "0x716b528435345a640bd31a94e699b10440e418ff0edf62a2874091a682459084", + oddYParity: true, + r: "0x28b384f1bf4b0ff05cf0d9002a9bdc2cfd20ee105f3dbdca737d59eded43785f", + s: "0x73bcb4d0d6419becc9ee4db2ff80961443686b92d24c22a43f2e769cf080bbd8", }); }); }, @@ -100,7 +104,7 @@ describeDevMoonbeam( chainId: 1281, nonce: 0, maxPriorityFeePerGas: 0, - maxFeePerGas: 1000000000, + maxFeePerGas: DEFAULT_TXN_MAX_BASE_FEE, gasLimit: 21000, action: { call: baltathar.address.toLowerCase(), @@ -109,8 +113,8 @@ describeDevMoonbeam( input: "0x", accessList: [], oddYParity: false, - r: "0xff6a476d2d8d7b0a23fcb3f1471d1ddd4dec7f3803db7f769aa1ce2575e493ac", - s: "0x4ebec202edd10edfcee87927090105b95d8b58f80550cdf4eda20327f3377ca6", + r: "0x40f376b6ece87cedb35b8687bc50cfef91450a43af5b04b7d368c2164b9f100e", + s: "0x76f61710d719e35878d022a90a278d8e291502314a46b33f83f03894a6b36871", }); }); }, diff --git a/tests/tests/test-ethers/test-ethers.ts b/tests/tests/test-ethers/test-ethers.ts index 955ddc553d..c0c8bd482e 100644 --- a/tests/tests/test-ethers/test-ethers.ts +++ b/tests/tests/test-ethers/test-ethers.ts @@ -28,7 +28,7 @@ describeDevMoonbeam("Ethers.js contract", (context) => { let contract = await new Promise(async (resolve) => { const contractPromise = contractFactory.deploy({ gasLimit: 1_000_000, - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, }); await context.createBlock(); resolve(await contractPromise); @@ -51,7 +51,7 @@ describeDevMoonbeam("Ethers.js contract", (context) => { let contract = await new Promise(async (resolve) => { const contractPromise = contractFactory.deploy({ gasLimit: 1_000_000, - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, }); await context.createBlock(); resolve(await contractPromise); @@ -60,7 +60,7 @@ describeDevMoonbeam("Ethers.js contract", (context) => { // Must create the block and then wait, because etherjs will wait until // the contract is mined to return; let result = await new Promise(async (resolve) => { - const callPromise = contract.multiply(3, { gasLimit: 1_000_000, gasPrice: 1_000_000_000 }); + const callPromise = contract.multiply(3, { gasLimit: 1_000_000, gasPrice: 10_000_000_000 }); await context.createBlock(); resolve(await callPromise); }); @@ -74,7 +74,7 @@ describeDevMoonbeam("Ethers.js contract", (context) => { ); expect( ( - await contractFromAddress.multiply(3, { gasLimit: 1_000_000, gasPrice: 1_000_000_000 }) + await contractFromAddress.multiply(3, { gasLimit: 1_000_000, gasPrice: 10_000_000_000 }) ).toString() ).to.equal("21"); }); diff --git a/tests/tests/test-evm/test-pallet-evm-transfer.ts b/tests/tests/test-evm/test-pallet-evm-transfer.ts index 95aba1e909..8708d9c24d 100644 --- a/tests/tests/test-evm/test-pallet-evm-transfer.ts +++ b/tests/tests/test-evm/test-pallet-evm-transfer.ts @@ -47,7 +47,7 @@ describeDevMoonbeam("Pallet EVM - call", (context) => { "0x0", 100_000_000_000_000_000_000n, 12_000_000n, - 1_000_000_000n, + 100_000_000_000_000n, "0", undefined, [] diff --git a/tests/tests/test-fees/test-fee-multiplier.ts b/tests/tests/test-fees/test-fee-multiplier.ts new file mode 100644 index 0000000000..2abd6d593f --- /dev/null +++ b/tests/tests/test-fees/test-fee-multiplier.ts @@ -0,0 +1,162 @@ +import "@moonbeam-network/api-augment"; +import { expect } from "chai"; +import { BN, bnToHex } from "@polkadot/util"; +import { + TREASURY_ACCOUNT, + MIN_GLMR_STAKING, + PRECOMPILE_PARACHAIN_STAKING_ADDRESS, +} from "../../util/constants"; +import { describeDevMoonbeamAllEthTxTypes, describeDevMoonbeam } from "../../util/setup-dev-tests"; +import { createTransfer, sendPrecompileTx } from "../../util/transactions"; +import { + baltathar, + BALTATHAR_PRIVATE_KEY, + charleth, + CHARLETH_PRIVATE_KEY, +} from "../../util/accounts"; +import { u128 } from "@polkadot/types"; +import { alith } from "../../util/accounts"; +import { createContract, createContractExecution } from "../../util/transactions"; +import { customWeb3Request } from "../../util/providers"; + +// Note on the values from 'transactionPayment.nextFeeMultiplier': this storage item is actually a +// FixedU128, which is basically a u128 with an implicit denominator of 10^18. However, this +// denominator is omitted when it is queried through the API, leaving some very large numbers. +// +// To make sense of them, basically remove 18 zeroes (divide by 10^18). This will give you the +// number used internally by transaction-payment for fee calculations. + +describeDevMoonbeam("Max Fee Multiplier", (context) => { + beforeEach("set to max multiplier", async () => { + const MULTIPLIER_STORAGE_KEY = context.polkadotApi.query.transactionPayment.nextFeeMultiplier + .key(0) + .toString(); + + const U128_MAX = new BN("340282366920938463463374607431768211455"); + const newMultiplierValue = context.polkadotApi.createType("u128", U128_MAX); + + // set transaction-payment's multiplier to something above max in storage. on the next round, + // it should enforce its upper bound and reset it. + await context.polkadotApi.tx.sudo + .sudo( + context.polkadotApi.tx.system.setStorage([ + [MULTIPLIER_STORAGE_KEY, bnToHex(newMultiplierValue)], + ]) + ) + .signAndSend(alith); + await context.createBlock(); + }); + + it("should enforce upper bound", async () => { + // we set it to u128_max, but the max should have been enforced in on_finalize() + const multiplier = ( + await context.polkadotApi.query.transactionPayment.nextFeeMultiplier() + ).toBigInt(); + expect(multiplier).to.equal(100_000_000_000_000_000_000_000n); + }); + + it("should have spendable runtime upgrade", async () => { + const multiplier = ( + await context.polkadotApi.query.transactionPayment.nextFeeMultiplier() + ).toBigInt(); + expect(multiplier).to.equal(100_000_000_000_000_000_000_000n); + + const initialBalance = ( + await context.polkadotApi.query.system.account(baltathar.address) + ).data.free.toBigInt(); + + // generate a mock runtime upgrade hex string + let size = 4194304; // 2MB bytes represented in hex + let hex = "0x" + "F".repeat(size); + + // send an enactAuthorizedUpgrade. we expect this to fail, but we just want to see that it was + // included in a block (not rejected) and was charged based on its length + await context.polkadotApi.tx.parachainSystem.enactAuthorizedUpgrade(hex).signAndSend(baltathar); + await context.createBlock(); + + let afterBalance = ( + await context.polkadotApi.query.system.account(baltathar.address) + ).data.free.toBigInt(); + + // note that this is not really affected by the high multiplier because most of its fee is + // derived from the length_fee, which is not scaled by the multiplier + expect(initialBalance - afterBalance).to.equal(9_231_801_265_723_667_008n); + }); + + it("should have spendable fill_block", async () => { + const multiplier = ( + await context.polkadotApi.query.transactionPayment.nextFeeMultiplier() + ).toBigInt(); + expect(multiplier).to.equal(100_000_000_000_000_000_000_000n); + + // fill_block will not charge its full amount for us, but we can inspect the initial balance + // withdraw event to see what it would charge. it is root only and will refund if not called by + // root, but sudo will also cause a refund. + + let fillAmount = 600_000_000; // equal to 60% Perbill + + const { block, result } = await context.createBlock( + context.polkadotApi.tx.system.fillBlock(fillAmount) + ); + + // grab the first withdraw event and hope it's the right one... + const withdrawEvent = result.events.filter(({ event }) => event.method == "Withdraw")[0]; + let amount = (withdrawEvent.event.data as any).amount.toBigInt(); + expect(amount).to.equal(1_500_000_012_598_000_941_192n); + }); + + // similar to tests in test-contract-fibonacci.ts, which implements an Ethereum txn which uses + // most of the block gas limit. This is done with the fee at its max, however. + it("fibonacci[370] should be spendable", async function () { + let blockNumber = (await context.polkadotApi.rpc.chain.getHeader()).number.toBn(); + let baseFeePerGas = BigInt((await context.web3.eth.getBlock(blockNumber)).baseFeePerGas); + expect(baseFeePerGas).to.equal(125_000_000_000_000n); + + const { contract, rawTx } = await createContract(context, "Fibonacci", { + gasPrice: "0x" + baseFeePerGas.toString(16), + }); + const { + result: { hash: createTxHash }, + } = await context.createBlock(rawTx); + + let receipt = await context.web3.eth.getTransactionReceipt(createTxHash); + expect(receipt.status).to.be.true; + + // the multiplier (and thereby base_fee) will have decreased very slightly... + blockNumber = (await context.polkadotApi.rpc.chain.getHeader()).number.toBn(); + baseFeePerGas = BigInt((await context.web3.eth.getBlock(blockNumber)).baseFeePerGas); + expect(baseFeePerGas).to.equal(124_880_845_878_351n); + + const tx = await createContractExecution( + context, + { + contract, + contractCall: contract.methods.fib2(370), + }, + { gasPrice: "0x" + baseFeePerGas.toString(16) } + ); + let { result } = await context.createBlock(tx); + + receipt = await context.web3.eth.getTransactionReceipt(result.hash); + expect(receipt.status).to.be.true; + + const successEvent = result.events.filter(({ event }) => event.method == "ExtrinsicSuccess")[0]; + let weight = (successEvent.event.data as any).dispatchInfo.weight.refTime.toBigInt(); + expect(weight).to.equal(4_162_425_000n); + + const withdrawEvents = result.events.filter(({ event }) => event.method == "Withdraw"); + expect(withdrawEvents.length).to.equal(1); + const withdrawEvent = withdrawEvents[0]; + let amount = (withdrawEvent.event.data as any).amount.toBigInt(); + expect(amount).to.equal(20_828_626_522_358_406_588n); + }); +}); + +describeDevMoonbeam("Max Fee Multiplier - initial value", (context) => { + it("should start with genesis value", async () => { + const initialValue = ( + await context.polkadotApi.query.transactionPayment.nextFeeMultiplier() + ).toBigInt(); + expect(initialValue).to.equal(8_000_000_000_000_000_000n); + }); +}); diff --git a/tests/tests/test-fees/test-fees-repartition.ts b/tests/tests/test-fees/test-fees-repartition.ts index 2177774f54..aeef5911cb 100644 --- a/tests/tests/test-fees/test-fees-repartition.ts +++ b/tests/tests/test-fees/test-fees-repartition.ts @@ -14,7 +14,7 @@ describeDevMoonbeamAllEthTxTypes("Fees - Transaction", (context) => { // We make an ethereum transaction, 20% of the fees should go to treasury. await context.createBlock(createTransfer(context, baltathar.address, 128)); - expect(await context.web3.eth.getBalance(TREASURY_ACCOUNT, 1)).to.equal("4200000000000"); + expect(await context.web3.eth.getBalance(TREASURY_ACCOUNT, 1)).to.equal("42000000000000"); }); }); @@ -27,7 +27,7 @@ describeDevMoonbeamAllEthTxTypes("Fees - Transaction", (context) => { // We make an ethereum transaction, 20% of the fees should go to treasury. await context.createBlock(createTransfer(context, baltathar.address, 128)); expect(await (await context.polkadotApi.query.balances.totalIssuance()).toBigInt()).to.equal( - originalTotalIssuance - 16800000000000n + originalTotalIssuance - 168000000000000n ); }); }); diff --git a/tests/tests/test-fees/test-length-fees.ts b/tests/tests/test-fees/test-length-fees.ts index e37892d4b9..fa7ecbd7cc 100644 --- a/tests/tests/test-fees/test-length-fees.ts +++ b/tests/tests/test-fees/test-length-fees.ts @@ -12,7 +12,7 @@ describeDevMoonbeam( (context) => { it("should have low balance transfer fees", async () => { const fee = await testBalanceTransfer(context); - expect(fee).to.equal(20958001520875n); + expect(fee).to.equal(79359001520875n); }); }, "Legacy", @@ -24,7 +24,7 @@ describeDevMoonbeam( (context) => { it("should have expensive runtime-upgrade fees", async () => { const fee = await testRuntimeUpgrade(context); - expect(fee).to.equal(9226801315723667008n); + expect(fee).to.equal(9226801665723667008n); }); }, "Legacy", @@ -36,7 +36,7 @@ describeDevMoonbeam( (context) => { it("should have low balance transfer fees", async () => { const fee = await testBalanceTransfer(context); - expect(fee).to.equal(20958001520875n); + expect(fee).to.equal(79_359_001_520_875n); }); }, "Legacy", @@ -48,7 +48,7 @@ describeDevMoonbeam( (context) => { it("should have expensive runtime-upgrade fees", async () => { const fee = await testRuntimeUpgrade(context); - expect(fee).to.equal(9226801315723667008n); + expect(fee).to.equal(9_226_801_665_723_667_008n); }); }, "Legacy", @@ -60,7 +60,7 @@ describeDevMoonbeam( (context) => { it("should have low balance transfer fees", async () => { const fee = await testBalanceTransfer(context); - expect(fee).to.equal(2095800152087500n); + expect(fee).to.equal(7_935_900_152_087_500n); }); }, "Legacy", @@ -72,7 +72,7 @@ describeDevMoonbeam( (context) => { it("should have expensive runtime-upgrade fees", async () => { const fee = await testRuntimeUpgrade(context); - expect(fee).to.equal(922680131572366700800n); + expect(fee).to.equal(922_680_166_572_366_700_800n); }); }, "Legacy", diff --git a/tests/tests/test-maintenance/test-maintenance-filter.ts b/tests/tests/test-maintenance/test-maintenance-filter.ts index 363147f16b..a41e4300ec 100644 --- a/tests/tests/test-maintenance/test-maintenance-filter.ts +++ b/tests/tests/test-maintenance/test-maintenance-filter.ts @@ -47,7 +47,7 @@ describeDevMoonbeam("Maintenance Mode - Filter", (context) => { "0x0", 100n * GLMR, 12_000_000n, - 1_000_000_000n, + 10_000_000_000n, "0", undefined, [] diff --git a/tests/tests/test-moon/test-moon-rpc.ts b/tests/tests/test-moon/test-moon-rpc.ts index 3436e2f775..424ea95350 100644 --- a/tests/tests/test-moon/test-moon-rpc.ts +++ b/tests/tests/test-moon/test-moon-rpc.ts @@ -2,7 +2,7 @@ import "@moonbeam-network/api-augment"; import { describeDevMoonbeam, describeDevMoonbeamAllEthTxTypes } from "../../util/setup-dev-tests"; import { expect, use as chaiUse } from "chai"; import chaiAsPromised from "chai-as-promised"; -import { createTransaction } from "../../util/transactions"; +import { createTransaction, DEFAULT_TXN_MAX_BASE_FEE } from "../../util/transactions"; import { ALITH_PRIVATE_KEY, BALTATHAR_ADDRESS } from "../../util/accounts"; chaiUse(chaiAsPromised); @@ -49,7 +49,7 @@ describeDevMoonbeamAllEthTxTypes("Moon RPC Methods - moon_isTxFinalized", (conte privateKey: ALITH_PRIVATE_KEY, to: BALTATHAR_ADDRESS, gas: 12_000_000, - gasPrice: 1_000_000_000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, value: 1_000_000, }), { finalize: true } @@ -66,7 +66,7 @@ describeDevMoonbeamAllEthTxTypes("Moon RPC Methods - moon_isTxFinalized", (conte privateKey: ALITH_PRIVATE_KEY, to: BALTATHAR_ADDRESS, gas: 12_000_000, - gasPrice: 1_000_000_000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, value: 1_000_000, }), { finalize: false } @@ -89,7 +89,7 @@ describeDevMoonbeamAllEthTxTypes("Moon RPC Methods - moon_isTxFinalized", (conte privateKey: ALITH_PRIVATE_KEY, to: BALTATHAR_ADDRESS, gas: 12_000_000, - gasPrice: 1_000_000_000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, value: 1_000_000, }), { finalize: false } @@ -108,7 +108,7 @@ describeDevMoonbeamAllEthTxTypes("Moon RPC Methods - moon_isTxFinalized", (conte privateKey: ALITH_PRIVATE_KEY, to: BALTATHAR_ADDRESS, gas: 12_000_000, - gasPrice: 1_000_000_000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, value: 1_000_000, }), { finalize: false } diff --git a/tests/tests/test-precompile/test-precompile-erc20.ts b/tests/tests/test-precompile/test-precompile-erc20.ts index 3f0f613ba5..ba90eb8a83 100644 --- a/tests/tests/test-precompile/test-precompile-erc20.ts +++ b/tests/tests/test-precompile/test-precompile-erc20.ts @@ -130,7 +130,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - ERC20 Native", (context) => { const receipt = await context.web3.eth.getTransactionReceipt(result.hash); expect(receipt.status).to.equal(true); - const fees = receipt.gasUsed * 1_000_000_000; + const fees = receipt.gasUsed * 10_000_000_000; expect(await getBalance(context, 1, alith.address)).to.equal( (await getBalance(context, 0, alith.address)) - BigInt(`0x${amount}`) - BigInt(fees) diff --git a/tests/tests/test-precompile/test-precompile-local-assets-erc20.ts b/tests/tests/test-precompile/test-precompile-local-assets-erc20.ts index af853d47f6..671dd97db7 100644 --- a/tests/tests/test-precompile/test-precompile-local-assets-erc20.ts +++ b/tests/tests/test-precompile/test-precompile-local-assets-erc20.ts @@ -43,7 +43,7 @@ const SELECTORS = { logApprove: "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", logTransfer: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", }; -const GAS_PRICE = "0x" + (1_000_000_000).toString(16); +const GAS_PRICE = "0x" + (10_000_000_000).toString(16); const LOCAL_ASSET_EXTENDED_ERC20_CONTRACT = getCompiled("LocalAssetExtendedErc20Instance"); const ROLES_CONTRACT = getCompiled("Roles"); diff --git a/tests/tests/test-precompile/test-precompile-xtokens.ts b/tests/tests/test-precompile/test-precompile-xtokens.ts index 1199875007..42da6f8c7e 100644 --- a/tests/tests/test-precompile/test-precompile-xtokens.ts +++ b/tests/tests/test-precompile/test-precompile-xtokens.ts @@ -5,17 +5,18 @@ import { ethers } from "ethers"; import { alith } from "../../util/accounts"; import { verifyLatestBlockFees } from "../../util/block"; -import { - MIN_GAS_PRICE, - PRECOMPILE_NATIVE_ERC20_ADDRESS, - PRECOMPILE_XTOKENS_ADDRESS, -} from "../../util/constants"; +import { PRECOMPILE_NATIVE_ERC20_ADDRESS, PRECOMPILE_XTOKENS_ADDRESS } from "../../util/constants"; import { getCompiled } from "../../util/contracts"; -import { describeDevMoonbeamAllEthTxTypes, DevTestContext } from "../../util/setup-dev-tests"; +import { + describeDevMoonbeam, + describeDevMoonbeamAllEthTxTypes, + DevTestContext, +} from "../../util/setup-dev-tests"; import { ALITH_TRANSACTION_TEMPLATE, createContract, createTransaction, + DEFAULT_TXN_MAX_BASE_FEE, } from "../../util/transactions"; const XTOKENS_CONTRACT = getCompiled("XtokensInstance"); @@ -27,7 +28,7 @@ async function getBalance(context: DevTestContext, blockHeight: number, address: return account.data.free.toBigInt(); } -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer xtokens", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -80,7 +81,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( @@ -90,7 +91,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer xtokens with fee", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -148,7 +149,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( @@ -158,7 +159,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer_multiasset xtokens", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -227,7 +228,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( @@ -237,7 +238,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer_multiasset xtokens with fee", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -312,7 +313,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( @@ -322,7 +323,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer multicurrencies xtokens", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -378,7 +379,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( @@ -388,7 +389,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { }); }); -describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { +describeDevMoonbeam("Precompiles - xtokens", (context) => { it("allows to issue transfer multiassets xtokens", async function () { const { rawTx } = await createContract(context, "XtokensInstance"); await context.createBlock(rawTx); @@ -459,7 +460,7 @@ describeDevMoonbeamAllEthTxTypes("Precompiles - xtokens", (context) => { ); const receipt = await context.web3.eth.getTransactionReceipt(result.hash); - const fees = BigInt(receipt.gasUsed) * MIN_GAS_PRICE; + const fees = BigInt(receipt.gasUsed) * BigInt(DEFAULT_TXN_MAX_BASE_FEE); // our tokens + fees should have been spent expect(await getBalance(context, 2, alith.address)).to.equal( diff --git a/tests/tests/test-receipt/test-receipt.ts b/tests/tests/test-receipt/test-receipt.ts index b1823d6cc9..7cd8d06e63 100644 --- a/tests/tests/test-receipt/test-receipt.ts +++ b/tests/tests/test-receipt/test-receipt.ts @@ -43,7 +43,7 @@ describeDevMoonbeam( const preBalance = BigInt(await context.web3.eth.getBalance(alith.address)); // With this configuration only half of the priority fee will be used, as the max_fee_per_gas // is 2GWEI and the base fee is 1GWEI. - const maxFeePerGas = 1_000_000_000 * 2; + const maxFeePerGas = 10_000_000_000 * 2; await context.createBlock( createTransaction(context, { diff --git a/tests/tests/test-staking/test-rewards-auto-compound.ts b/tests/tests/test-staking/test-rewards-auto-compound.ts index 81ec492643..5575ba384e 100644 --- a/tests/tests/test-staking/test-rewards-auto-compound.ts +++ b/tests/tests/test-staking/test-rewards-auto-compound.ts @@ -349,13 +349,12 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - candidate leave", (contex describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", (context) => { let newDelegator: KeyringPair; - let delegationCount = 0; before("should delegate and add baltathar as candidate", async () => { - const [delegator, ...otherDelegators] = new Array( + const maxDelegationCount = context.polkadotApi.consts.parachainStaking.maxTopDelegationsPerCandidate.toNumber() + - context.polkadotApi.consts.parachainStaking.maxBottomDelegationsPerCandidate.toNumber() - ) + context.polkadotApi.consts.parachainStaking.maxBottomDelegationsPerCandidate.toNumber(); + const [delegator, ...otherDelegators] = new Array(maxDelegationCount) .fill(0) .map(() => generateKeyringPair()); newDelegator = delegator; @@ -369,7 +368,7 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", .joinCandidates(MIN_GLMR_STAKING, 1) .signAsync(baltathar), context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR, delegationCount++, 0) + .delegate(alith.address, MIN_GLMR_DELEGATOR, 0, 0) .signAsync(ethan), ]) ); @@ -388,7 +387,8 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", ]) ); - // fill all delegations, we split this into two blocks as it will not fit into one + // fill all delegations, we split this into two blocks as it will not fit into one. + // we use a maxDelegationCount here, since the transactions can come out of order. await expectOk( context.createBlock([ context.polkadotApi.tx.parachainStaking @@ -398,7 +398,7 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", .slice(0, 150) .map((d) => context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, delegationCount++, 1) + .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, maxDelegationCount, 1) .signAsync(d) ), ]) @@ -409,7 +409,7 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", .slice(150) .map((d) => context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, delegationCount++, 1) + .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, maxDelegationCount, 1) .signAsync(d) ), ]) @@ -441,11 +441,15 @@ describeDevMoonbeam("Staking - Rewards Auto-Compound - bottom delegation kick", expect(autoCompoundDelegationsAlithBefore.toJSON()).to.not.be.empty; expect(autoCompoundDelegationsBaltatharBefore.toJSON()).to.not.be.empty; + const maxDelegationCount = + context.polkadotApi.consts.parachainStaking.maxTopDelegationsPerCandidate.toNumber() + + context.polkadotApi.consts.parachainStaking.maxBottomDelegationsPerCandidate.toNumber(); + // This kicks ethan from bottom delegations for alith await expectOk( context.createBlock( context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, delegationCount++, 0) + .delegate(alith.address, MIN_GLMR_DELEGATOR + 10n * GLMR, maxDelegationCount, 0) .signAsync(newDelegator) ) ); diff --git a/tests/tests/test-staking/test-staking-locks.ts b/tests/tests/test-staking/test-staking-locks.ts index ebd37ebefb..2882e4ce15 100644 --- a/tests/tests/test-staking/test-staking-locks.ts +++ b/tests/tests/test-staking/test-staking-locks.ts @@ -485,7 +485,7 @@ describeDevMoonbeam("Staking - Locks - bottom delegator removed", (context) => { const txns = await [...additionalDelegators].map((account, i) => context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR + GLMR, i + 1, 1) + .delegate(alith.address, MIN_GLMR_DELEGATOR + GLMR, additionalDelegators.length + 1, 1) .signAsync(account) ); @@ -545,7 +545,7 @@ describeDevMoonbeam("Staking - Locks - bottom and top delegations", (context) => context.createBlock( [...topDelegators].map((account, i) => context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR + 1n * GLMR, i + 1, 1) + .delegate(alith.address, MIN_GLMR_DELEGATOR + 1n * GLMR, topDelegators.length + 1, 1) .signAsync(account) ) ) @@ -554,7 +554,12 @@ describeDevMoonbeam("Staking - Locks - bottom and top delegations", (context) => context.createBlock( [...bottomDelegators].map((account, i) => context.polkadotApi.tx.parachainStaking - .delegate(alith.address, MIN_GLMR_DELEGATOR, topDelegators.length + i + 1, 1) + .delegate( + alith.address, + MIN_GLMR_DELEGATOR, + topDelegators.length + bottomDelegators.length + 1, + 1 + ) .signAsync(account) ) ) diff --git a/tests/tests/test-subscription/test-subscription.ts b/tests/tests/test-subscription/test-subscription.ts index 1c1205336d..211db916d1 100644 --- a/tests/tests/test-subscription/test-subscription.ts +++ b/tests/tests/test-subscription/test-subscription.ts @@ -41,7 +41,7 @@ describeDevMoonbeam("Subscription - Block headers", (context) => { miner: "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", receiptsRoot: "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - transactionsRoot: "0x14363f4c0580a470a7879ba247f97c2d62d77963a73464c49507f721d7f85bfc", + transactionsRoot: "0x795ca28f628cccc283e1d0338f9b027851b55b25f99890c19bbbca3686cfb939", }); expect(data.nonce).to.be.eq("0x0000000000000000"); }); diff --git a/tests/tests/test-sudo/test-sudo.ts b/tests/tests/test-sudo/test-sudo.ts index dfb28b3cea..9d811b341b 100644 --- a/tests/tests/test-sudo/test-sudo.ts +++ b/tests/tests/test-sudo/test-sudo.ts @@ -40,7 +40,7 @@ describeDevMoonbeam("Sudo - fail if no funds in sudo", (context) => { createTransfer( context, baltathar.address, - BigInt(initBalance) - 1n - 21000n * 1_000_000_000n, + BigInt(initBalance) - 1n - 21000n * 10_000_000_000n, { gas: 21000, } diff --git a/tests/tests/test-txpool/test-txpool-future.ts b/tests/tests/test-txpool/test-txpool-future.ts index 37ba481e17..78349a6bfe 100644 --- a/tests/tests/test-txpool/test-txpool-future.ts +++ b/tests/tests/test-txpool/test-txpool-future.ts @@ -23,7 +23,7 @@ describeDevMoonbeam("TxPool - Future Ethereum transaction", (context) => { let data = inspect.result.queued[alith.address.toLowerCase()][context.web3.utils.toHex(1)]; expect(data).to.not.be.undefined; expect(data).to.be.equal( - "0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 1000000000 wei" + "0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 10000000000 wei" ); }); @@ -36,7 +36,7 @@ describeDevMoonbeam("TxPool - Future Ethereum transaction", (context) => { blockNumber: null, from: alith.address.toLowerCase(), gas: "0x100000", - gasPrice: "0x3b9aca00", + gasPrice: "0x2540be400", hash: txHash, nonce: context.web3.utils.toHex(1), to: "0x0000000000000000000000000000000000000000", diff --git a/tests/tests/test-txpool/test-txpool-pending.ts b/tests/tests/test-txpool/test-txpool-pending.ts index 97e9eee538..990d21f828 100644 --- a/tests/tests/test-txpool/test-txpool-pending.ts +++ b/tests/tests/test-txpool/test-txpool-pending.ts @@ -23,7 +23,7 @@ describeDevMoonbeam("TxPool - Pending Ethereum transaction", (context) => { let data = inspect.result.pending[alith.address.toLowerCase()][context.web3.utils.toHex(0)]; expect(data).to.not.be.undefined; expect(data).to.be.equal( - "0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 1000000000 wei" + "0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 10000000000 wei" ); }); @@ -47,7 +47,7 @@ describeDevMoonbeam("TxPool - Pending Ethereum transaction", (context) => { blockNumber: null, from: alith.address.toLowerCase(), gas: "0x100000", - gasPrice: "0x3b9aca00", + gasPrice: "0x2540be400", hash: txHash, nonce: context.web3.utils.toHex(0), to: "0x0000000000000000000000000000000000000000", @@ -88,7 +88,7 @@ describeDevMoonbeam("TxPool - Ethereum Contract Call", (context) => { expect(data).to.not.be.undefined; expect(data).to.be.equal( - contractAddress.toLowerCase() + ": 0 wei + 12000000 gas x 1000000000 wei" + contractAddress.toLowerCase() + ": 0 wei + 12000000 gas x 10000000000 wei" ); }); @@ -100,7 +100,7 @@ describeDevMoonbeam("TxPool - Ethereum Contract Call", (context) => { blockNumber: null, from: alith.address.toLowerCase(), gas: "0xb71b00", - gasPrice: "0x3b9aca00", + gasPrice: "0x2540be400", hash: txHash, nonce: context.web3.utils.toHex(1), to: multiplyBy7Contract.options.address.toLowerCase(), diff --git a/tests/util/block.ts b/tests/util/block.ts index 6311c4e160..452593b312 100644 --- a/tests/util/block.ts +++ b/tests/util/block.ts @@ -40,6 +40,15 @@ export async function createAndFinalizeBlock( }; } +// Given a deposit amount, returns the amount burned (80%) and deposited to treasury (20%). +// This is meant to precisely mimic the logic in the Moonbeam runtimes where the burn amount +// is calculated and the treasury is treated as the remainder. This precision is important to +// avoid off-by-one errors. +export function calculateFeePortions(amount: bigint): { burnt: bigint; treasury: bigint } { + const burnt = (amount * 80n) / 100n; // 20% goes to treasury + return { burnt, treasury: amount - burnt }; +} + export interface TxWithEventAndFee extends TxWithEvent { fee: RuntimeDispatchInfo; } @@ -194,32 +203,54 @@ export const verifyBlockFees = async ( ) { if (extrinsic.method.section == "ethereum") { // For Ethereum tx we caluculate fee by first converting weight to gas - const gasFee = (dispatchInfo as any).weight.refTime.toBigInt() / WEIGHT_PER_GAS; + const gasUsed = (dispatchInfo as any).weight.refTime.toBigInt() / WEIGHT_PER_GAS; let ethTxWrapper = extrinsic.method.args[0] as any; - let gasPrice; + + let number = blockDetails.block.header.number.toNumber(); + // The on-chain base fee used by the transaction. Aka the parent block's base fee. + // + // Note on 1559 fees: no matter what the user was willing to pay (maxFeePerGas), + // the transaction fee is ultimately computed using the onchain base fee. The + // additional tip eventually paid by the user (maxPriorityFeePerGas) is purely a + // prioritization component: the EVM is not aware of it and thus not part of the + // weight cost of the extrinsic. + let baseFeePerGas = BigInt( + (await context.web3.eth.getBlock(number - 1)).baseFeePerGas + ); + let priorityFee; + // Transaction is an enum now with as many variants as supported transaction types. if (ethTxWrapper.isLegacy) { - gasPrice = ethTxWrapper.asLegacy.gasPrice.toBigInt(); + priorityFee = ethTxWrapper.asLegacy.gasPrice.toBigInt(); } else if (ethTxWrapper.isEip2930) { - gasPrice = ethTxWrapper.asEip2930.gasPrice.toBigInt(); + priorityFee = ethTxWrapper.asEip2930.gasPrice.toBigInt(); } else if (ethTxWrapper.isEip1559) { - let number = blockDetails.block.header.number.toNumber(); - // The on-chain base fee used by the transaction. Aka the parent block's base fee. - // - // Note on 1559 fees: no matter what the user was willing to pay (maxFeePerGas), - // the transaction fee is ultimately computed using the onchain base fee. The - // additional tip eventually paid by the user (maxPriorityFeePerGas) is purely a - // prioritization component: the EVM is not aware of it and thus not part of the - // weight cost of the extrinsic. - gasPrice = BigInt((await context.web3.eth.getBlock(number - 1)).baseFeePerGas); + priorityFee = ethTxWrapper.asEip1559.maxPriorityFeePerGas.toBigInt(); } - // And then multiplying by gasPrice - txFees = gasFee * gasPrice; + + let effectiveTipPerGas = priorityFee - baseFeePerGas; + if (effectiveTipPerGas < 0n) { + effectiveTipPerGas = 0n; + } + + // 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. + const baseFeesPaid = gasUsed * baseFeePerGas; + const tipAsFeesPaid = gasUsed * effectiveTipPerGas; + + const baseFeePortions = calculateFeePortions(baseFeesPaid); + const tipFeePortions = calculateFeePortions(tipAsFeesPaid); + + txFees += baseFeesPaid + tipAsFeesPaid; + txBurnt += baseFeePortions.burnt; + txBurnt += tipFeePortions.burnt; } else { // For a regular substrate tx, we use the partialFee + let feePortions = calculateFeePortions(fee.partialFee.toBigInt()); txFees = fee.partialFee.toBigInt(); + txBurnt += feePortions.burnt; } - txBurnt += (txFees * 80n) / 100n; // 20% goes to treasury blockFees += txFees; blockBurnt += txBurnt; @@ -248,31 +279,30 @@ export const verifyBlockFees = async ( // Then search for Deposit event from treasury // This is for bug detection when the fees are not matching the expected value // TODO: sudo should not have treasury event - for (const event of events) { - if ( - event.section == "treasury" && - event.method == "Deposit" && - extrinsic.method.section !== "sudo" - ) { - const deposit = (event.data[0] as any).toBigInt(); - // Compare deposit event amont to what should have been sent to deposit - // (if they don't match, which is not a desired behavior) - expect( - txFees - txBurnt, - `Desposit Amount Discrepancy!\n` + - ` Block: #${blockDetails.block.header.number.toString()}\n` + - `Extrinsic: ${extrinsic.method.section}.${extrinsic.method.method}\n` + - ` Args: \n` + - extrinsic.args.map((arg) => ` - ${arg.toString()}`).join("\n") + - ` Events: \n` + - events - .map(({ data, method, section }) => ` - ${section}.${method}:: ${data}`) - .join("\n") + - ` fees not burnt : ${(txFees - txBurnt).toString().padStart(30, " ")}\n` + - ` deposit : ${deposit.toString().padStart(30, " ")}` - ).to.eq(deposit); - } - } + const allDeposits = events + .filter( + (event) => + event.section == "treasury" && + event.method == "Deposit" && + extrinsic.method.section !== "sudo" + ) + .map((event) => (event.data[0] as any).toBigInt()) + .reduce((p, v) => p + v, 0n); + + expect( + txFees - txBurnt, + `Desposit Amount Discrepancy!\n` + + ` Block: #${blockDetails.block.header.number.toString()}\n` + + `Extrinsic: ${extrinsic.method.section}.${extrinsic.method.method}\n` + + ` Args: \n` + + extrinsic.args.map((arg) => ` - ${arg.toString()}\n`).join("") + + ` Events: \n` + + events + .map(({ data, method, section }) => ` - ${section}.${method}:: ${data}\n`) + .join("") + + ` fees not burnt : ${(txFees - txBurnt).toString().padStart(30, " ")}\n` + + ` all deposits : ${allDeposits.toString().padStart(30, " ")}` + ).to.eq(allDeposits); } sumBlockFees += blockFees; sumBlockBurnt += blockBurnt; diff --git a/tests/util/constants.ts b/tests/util/constants.ts index da2b506c33..f62d5cd290 100644 --- a/tests/util/constants.ts +++ b/tests/util/constants.ts @@ -49,7 +49,7 @@ export const EXTRINSIC_GAS_LIMIT = BLOCK_TX_GAS_LIMIT - BLOCK_TX_LIMIT * 0.1; // Weight per gas mapping export const WEIGHT_PER_GAS = 1_000_000_000_000n / 40_000_000n; -export const MIN_GAS_PRICE = 1_000_000_000n; +export const MIN_GAS_PRICE = 10_000_000_000n; export const PRECOMPILE_PARACHAIN_STAKING_ADDRESS = "0x0000000000000000000000000000000000000800"; export const PRECOMPILE_CROWDLOAN_REWARDS_ADDRESS = "0x0000000000000000000000000000000000000801"; diff --git a/tests/util/contracts.ts b/tests/util/contracts.ts index 88b7a5e6c5..8080349168 100644 --- a/tests/util/contracts.ts +++ b/tests/util/contracts.ts @@ -52,7 +52,7 @@ export async function deployContractManualSeal( from: account, data: contractByteCode, value: "0x00", - gasPrice: 1_000_000_000, + gasPrice: 10_000_000_000, gas: "0x100000", }, privateKey diff --git a/tests/util/expect.ts b/tests/util/expect.ts index 9297577faa..4b5ae108ef 100644 --- a/tests/util/expect.ts +++ b/tests/util/expect.ts @@ -18,9 +18,19 @@ export async function expectOk< const block = await call; if (Array.isArray(block.result)) { block.result.forEach((r, idx) => { - expect(r.successful, `tx[${idx}] - ${r.error?.name}`).to.be.true; + expect( + r.successful, + `tx[${idx}] - ${r.error?.name}${ + r.extrinsic + ? `\n\t\t${r.extrinsic.method.section}.${r.extrinsic.method.method}(${r.extrinsic.args + .map((d) => d.toHuman()) + .join("; ")})` + : "" + }` + ).to.be.true; }); } else { expect(block.result.successful, block.result.error?.name).to.be.true; } + return block; } diff --git a/tests/util/setup-dev-tests.ts b/tests/util/setup-dev-tests.ts index 69b1eb492f..5f6554190b 100644 --- a/tests/util/setup-dev-tests.ts +++ b/tests/util/setup-dev-tests.ts @@ -169,11 +169,23 @@ export function describeDevMoonbeam( .result, }); } else if (call.isSigned) { + const tx = context.polkadotApi.tx(call); + debug( + `- Signed: ${tx.method.section}.${tx.method.method}(${tx.args + .map((d) => d.toHuman()) + .join("; ")}) [ nonce: ${tx.nonce}]` + ); results.push({ type: "sub", hash: (await call.send()).toString(), }); } else { + const tx = context.polkadotApi.tx(call); + debug( + `- Unsigned: ${tx.method.section}.${tx.method.method}(${tx.args + .map((d) => d.toHuman()) + .join("; ")}) [ nonce: ${tx.nonce}]` + ); results.push({ type: "sub", hash: (await call.signAndSend(alith)).toString(), diff --git a/tests/util/transactions.ts b/tests/util/transactions.ts index a8f6e71394..32644066b3 100644 --- a/tests/util/transactions.ts +++ b/tests/util/transactions.ts @@ -24,6 +24,8 @@ import type { ApiPromise } from "@polkadot/api"; import type { SubmittableExtrinsic } from "@polkadot/api/promise/types"; const debug = require("debug")("test:transaction"); +export const DEFAULT_TXN_MAX_BASE_FEE = 10_000_000_000; + export interface TransactionOptions { from?: string; to?: string; @@ -41,7 +43,7 @@ export interface TransactionOptions { export const TRANSACTION_TEMPLATE: TransactionOptions = { nonce: null, gas: 500_000, - gasPrice: 1_000_000_000, + gasPrice: DEFAULT_TXN_MAX_BASE_FEE, value: "0x00", }; @@ -83,7 +85,7 @@ export const createTransaction = async ( const isEip2930 = context.ethTransactionType === "EIP2930"; const isEip1559 = context.ethTransactionType === "EIP1559"; - const gasPrice = options.gasPrice !== undefined ? options.gasPrice : 1_000_000_000; + const gasPrice = options.gasPrice !== undefined ? options.gasPrice : DEFAULT_TXN_MAX_BASE_FEE; const maxPriorityFeePerGas = options.maxPriorityFeePerGas !== undefined ? options.maxPriorityFeePerGas : 0; const value = options.value !== undefined ? options.value : "0x00"; @@ -99,7 +101,7 @@ export const createTransaction = async ( data: options.data, })); - const maxFeePerGas = options.maxFeePerGas || 1_000_000_000; + const maxFeePerGas = options.maxFeePerGas || BigInt(await context.web3.eth.getGasPrice()); const accessList = options.accessList || []; const nonce = options.nonce != null @@ -310,7 +312,7 @@ export async function sendPrecompileTx( ); } -const GAS_PRICE = "0x" + (1_000_000_000).toString(16); +const GAS_PRICE = "0x" + DEFAULT_TXN_MAX_BASE_FEE.toString(16); export async function callPrecompile( context: DevTestContext, precompileContractAddress: string,