From e0bc1435ff6232cd8c0b10c3e3b78faeeab0ddde Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Fri, 27 Sep 2024 15:47:22 +0200 Subject: [PATCH] Test USDT transaction payment --- Cargo.lock | 4 + integration-tests/Cargo.toml | 10 +- integration-tests/src/tests/credentials.rs | 18 +- .../src/tests/transaction_payment.rs | 184 +++++++++++++++++- nodes/parachain/src/chain_spec/common.rs | 2 - runtimes/polimec/Cargo.toml | 1 + runtimes/polimec/src/lib.rs | 15 +- runtimes/shared-configuration/Cargo.toml | 3 + runtimes/shared-configuration/src/fee.rs | 2 +- 9 files changed, 213 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92d0d838f..93a3f9b86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4613,7 +4613,9 @@ dependencies = [ "itertools 0.11.0", "macros", "orml-oracle", + "pallet-asset-tx-payment", "pallet-assets", + "pallet-aura", "pallet-balances", "pallet-collective", "pallet-democracy 0.8.0", @@ -4626,6 +4628,7 @@ dependencies = [ "pallet-message-queue", "pallet-parachain-staking", "pallet-scheduler", + "pallet-session", "pallet-staking", "pallet-transaction-payment", "pallet-treasury", @@ -4650,6 +4653,7 @@ dependencies = [ "serde", "sp-arithmetic", "sp-authority-discovery", + "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-core", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 3dfc45a81..e112587ad 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -75,12 +75,15 @@ pallet-democracy.workspace = true pallet-scheduler.workspace = true pallet-treasury.workspace = true frame-metadata-hash-extension.workspace = true - +pallet-asset-tx-payment.workspace = true # Runtimes polkadot-runtime.workspace = true asset-hub-polkadot-runtime.workspace = true polimec-runtime.workspace = true penpal-runtime = { path = "penpal", default-features = false } +sp-consensus-aura.workspace = true +pallet-aura.workspace = true +pallet-session.workspace = true [features] default = [ "development-settings", "instant-mode", "std" ] @@ -140,6 +143,10 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", + "pallet-asset-tx-payment/std", + "pallet-aura/std", + "pallet-session/std", + "sp-consensus-aura/std" ] development-settings = [ "polimec-runtime/development-settings" ] runtime-benchmarks = [ @@ -179,5 +186,6 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "pallet-asset-tx-payment/runtime-benchmarks" ] diff --git a/integration-tests/src/tests/credentials.rs b/integration-tests/src/tests/credentials.rs index a2138725a..3ad9d475e 100644 --- a/integration-tests/src/tests/credentials.rs +++ b/integration-tests/src/tests/credentials.rs @@ -79,20 +79,16 @@ fn dispenser_signed_extensions_pass_for_new_account() { frame_system::CheckEra::::from(Era::mortal(0u64, 0u64)), pallet_dispenser::extensions::CheckNonce::::from(0u32), frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0u64.into()).into(), + pallet_asset_tx_payment::ChargeAssetTxPayment::::from(0u64.into(), None).into(), frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); - assert_err!( - extra.validate(&who, &paid_call, &paid_call.get_dispatch_info(), 0), - TransactionValidityError::Invalid(Payment) - ); - assert_err!( - extra.clone().pre_dispatch(&who, &paid_call, &paid_call.get_dispatch_info(), 0), - TransactionValidityError::Invalid(Payment) - ); - assert_ok!(extra.validate(&who, &free_call, &free_call.get_dispatch_info(), 0)); - assert_ok!(extra.pre_dispatch(&who, &free_call, &free_call.get_dispatch_info(), 0)); + // `InitialPayment` struct from pallet_asset_tx_payment doesn't implement Debug and PartialEq to compare to a specific Error or use assert_ok! + assert!(extra.validate(&who, &paid_call, &paid_call.get_dispatch_info(), 0).is_err()); + assert!(extra.clone().pre_dispatch(&who, &paid_call, &paid_call.get_dispatch_info(), 0).is_err()); + + assert!(extra.validate(&who, &free_call, &free_call.get_dispatch_info(), 0).is_ok()); + assert!(extra.pre_dispatch(&who, &free_call, &free_call.get_dispatch_info(), 0).is_ok()); }); } diff --git a/integration-tests/src/tests/transaction_payment.rs b/integration-tests/src/tests/transaction_payment.rs index 67d03a936..0d847f002 100644 --- a/integration-tests/src/tests/transaction_payment.rs +++ b/integration-tests/src/tests/transaction_payment.rs @@ -1,4 +1,186 @@ +use crate::{ + constants, constants::PricesBuilder, polimec, PolimecAccountId, PolimecBalances, PolimecCall, PolimecForeignAssets, + PolimecNet, PolimecOrigin, PolimecRuntime, PolimecSystem, +}; +use frame_support::{ + dispatch::GetDispatchInfo, + traits::{fungible::Mutate as FungibleMutate, fungibles, fungibles::Mutate as FungiblesMutate}, +}; +use macros::generate_accounts; +use pallet_funding::{AcceptedFundingAsset, PriceProviderOf}; +use pallet_transaction_payment::FeeDetails; +use parity_scale_codec::Encode; +use polimec_common::{ProvideAssetPrice, PLMC_DECIMALS, PLMC_FOREIGN_ID, USD_DECIMALS}; +use polimec_runtime::Header; +use sp_arithmetic::{FixedPointNumber, FixedU128}; +use sp_core::H256; +use sp_runtime::{ + traits::{Dispatchable, Header as HeaderT, SignedExtension}, + DigestItem, +}; +use xcm_emulator::TestExt; +generate_accounts!(ALICE, AUTHOR); +use frame_support::traits::fungible::Inspect; + +// Setup code inspired by pallet-authorship tests +fn seal_header(mut header: Header, aura_index: u64) -> Header { + { + let digest = header.digest_mut(); + digest.logs.push(DigestItem::PreRuntime(sp_consensus_aura::AURA_ENGINE_ID, aura_index.encode())); + digest.logs.push(DigestItem::Seal(sp_consensus_aura::AURA_ENGINE_ID, aura_index.encode())); + } + + header +} + +fn create_header(number: u32, parent_hash: H256, state_root: H256) -> Header { + Header::new(number, Default::default(), state_root, parent_hash, Default::default()) +} +// Make sure to run this inside externalities environment. Can only be called one time per test. +fn set_author(aura_index: u64) { + let mut header = seal_header(create_header(1, Default::default(), [1; 32].into()), aura_index); + + header.digest_mut().pop(); // pop the seal off. + PolimecSystem::reset_events(); + PolimecSystem::initialize(&1, &Default::default(), header.digest()); +} + #[test] fn fee_paid_with_foreign_assets() { - todo!(); + polimec::set_prices( + PricesBuilder::new().usdt(FixedU128::from_float(1.0f64)).plmc(FixedU128::from_float(0.5f64)).build(), + ); + + PolimecNet::execute_with(|| { + let alice: PolimecAccountId = ALICE.into(); + + let (block_author, _) = &constants::collators::invulnerables()[1]; + // Block author's aura index is 1 + set_author(1u64); + assert_eq!(polimec_runtime::Authorship::author(), Some(block_author.clone())); + + let usdt_id = AcceptedFundingAsset::USDT.id(); + let usdt_decimals = >::decimals(usdt_id); + let usdt_unit = 10u128.pow(usdt_decimals as u32); + let plmc_decimals = PLMC_DECIMALS; + let plmc_unit = 10u128.pow(plmc_decimals as u32); + + PolimecBalances::set_balance(&alice, 0u128); + PolimecForeignAssets::set_balance(usdt_id, &alice, 100 * usdt_unit); + // Fees are usually very small, so we need to give the treasury an ED. + PolimecForeignAssets::set_balance( + usdt_id, + &polimec_runtime::BlockchainOperationTreasury::get(), + 100 * usdt_unit, + ); + // Block author doesn't need to have any balance, as the tip is bigger than ED. + + let paid_call = PolimecCall::System(frame_system::Call::remark { remark: vec![69, 69] }); + let paid_call_len = paid_call.encode().len(); + type TxPaymentExtension = pallet_asset_tx_payment::ChargeAssetTxPayment; + + // Tips are always defined in the native asset, and then converted to the fee asset if the second field is `Some`. + // Here a user wants to tip 10 PLMC in USDT. + let signed_extension = + pallet_asset_tx_payment::ChargeAssetTxPayment::::from(10 * plmc_unit, Some(usdt_id)); + + let dispatch_info = paid_call.get_dispatch_info(); + let FeeDetails { inclusion_fee, tip } = + polimec_runtime::TransactionPayment::compute_fee_details(paid_call_len as u32, &dispatch_info, 10u128 * plmc_unit); + let expected_plmc_fee = inclusion_fee.expect("call should charge a fee").inclusion_fee(); + let expected_plmc_tip = tip; + + let plmc_price_decimal_aware = + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, plmc_decimals) + .expect("Price irretrievable"); + + // USDT should be configured with the same decimals as our underlying USD unit, and we set the price to 1USD at the beginning of this test. + let expected_usd_fee = plmc_price_decimal_aware.saturating_mul_int(expected_plmc_fee); + let expected_usd_tip = plmc_price_decimal_aware.saturating_mul_int(expected_plmc_tip); + + let prev_alice_usdt_balance = PolimecForeignAssets::balance(usdt_id, alice.clone()); + let prev_alice_plmc_balance = PolimecBalances::balance(&alice); + let prev_blockchain_operation_treasury_usdt_balance = + PolimecForeignAssets::balance(usdt_id, polimec_runtime::BlockchainOperationTreasury::get()); + let prev_blockchain_operation_treasury_plmc_balance = + PolimecBalances::balance(&polimec_runtime::BlockchainOperationTreasury::get()); + let prev_block_author_usdt_balance = PolimecForeignAssets::balance(usdt_id, block_author.clone()); + let prev_block_author_plmc_balance = PolimecBalances::balance(&block_author.clone()); + + // Executes the `pre_dispatch` check for the transaction using the signed extension. + // Dispatches the `paid_call` signed by `alice` and ensures it executes successfully. + // Runs the `post_dispatch` logic to verify correct post-transaction handling, + // including fee calculation and cleanup using the `TxPaymentExtension`. + let pre = signed_extension.pre_dispatch(&alice, &paid_call, &dispatch_info, paid_call_len).unwrap(); + let post_info = paid_call.clone().dispatch(PolimecOrigin::signed(alice.clone())).expect("call dispatch failed"); + + TxPaymentExtension::post_dispatch(Some(pre), &dispatch_info, &post_info, paid_call_len, &Ok(())).unwrap(); + + let post_alice_usdt_balance = PolimecForeignAssets::balance(usdt_id, alice.clone()); + let post_alice_plmc_balance = PolimecBalances::balance(&alice); + let post_blockchain_operation_treasury_usdt_balance = + PolimecForeignAssets::balance(usdt_id, polimec_runtime::BlockchainOperationTreasury::get()); + let post_blockchain_operation_treasury_plmc_balance = + PolimecBalances::balance(&polimec_runtime::BlockchainOperationTreasury::get()); + let post_block_author_usdt_balance = PolimecForeignAssets::balance(usdt_id, block_author.clone()); + let post_block_author_plmc_balance = PolimecBalances::balance(&block_author.clone()); + + assert_eq!(prev_alice_usdt_balance - post_alice_usdt_balance, expected_usd_fee + expected_usd_tip); + assert_eq!(post_alice_plmc_balance, prev_alice_plmc_balance,); + assert_eq!( + post_blockchain_operation_treasury_usdt_balance - prev_blockchain_operation_treasury_usdt_balance, + expected_usd_fee + ); + assert_eq!( + post_blockchain_operation_treasury_plmc_balance, prev_blockchain_operation_treasury_plmc_balance + ); + assert_eq!(post_block_author_usdt_balance - prev_block_author_usdt_balance, expected_usd_tip); + assert_eq!(post_block_author_plmc_balance, prev_block_author_plmc_balance); + + // * Now we check if the same behavior but using PLMC as a fee asset, produces the same results. (2plmc=1usdt) * + PolimecBalances::set_balance(&alice, 100 * plmc_unit); + PolimecForeignAssets::set_balance(usdt_id, &alice, 0u128); + // Fees are usually very small, so we need to give the treasury an ED. + PolimecBalances::set_balance(&polimec_runtime::BlockchainOperationTreasury::get(), 100 * plmc_unit); + // Block author doesn't need to have any balance, as the tip is bigger than ED. + + // Now we set the fee asset to None, so the fee is paid with PLMC + let signed_extension = + pallet_asset_tx_payment::ChargeAssetTxPayment::::from(10 * plmc_unit, None); + + let prev_alice_usdt_balance = PolimecForeignAssets::balance(usdt_id, alice.clone()); + let prev_alice_plmc_balance = PolimecBalances::balance(&alice); + let prev_blockchain_operation_treasury_usdt_balance = + PolimecForeignAssets::balance(usdt_id, polimec_runtime::BlockchainOperationTreasury::get()); + let prev_blockchain_operation_treasury_plmc_balance = + PolimecBalances::balance(&polimec_runtime::BlockchainOperationTreasury::get()); + let prev_block_author_usdt_balance = PolimecForeignAssets::balance(usdt_id, block_author.clone()); + let prev_block_author_plmc_balance = PolimecBalances::balance(&block_author.clone()); + + let pre = signed_extension.pre_dispatch(&alice, &paid_call, &dispatch_info, paid_call_len).unwrap(); + let post_info = paid_call.dispatch(PolimecOrigin::signed(alice.clone())).expect("call dispatch failed"); + + TxPaymentExtension::post_dispatch(Some(pre), &dispatch_info, &post_info, paid_call_len, &Ok(())).unwrap(); + + let post_alice_usdt_balance = PolimecForeignAssets::balance(usdt_id, alice.clone()); + let post_alice_plmc_balance = PolimecBalances::balance(&alice); + let post_blockchain_operation_treasury_usdt_balance = + PolimecForeignAssets::balance(usdt_id, polimec_runtime::BlockchainOperationTreasury::get()); + let post_blockchain_operation_treasury_plmc_balance = + PolimecBalances::balance(&polimec_runtime::BlockchainOperationTreasury::get()); + let post_block_author_usdt_balance = PolimecForeignAssets::balance(usdt_id, block_author.clone()); + let post_block_author_plmc_balance = PolimecBalances::balance(&block_author.clone()); + + assert_eq!(post_alice_usdt_balance, prev_alice_usdt_balance); + assert_eq!(prev_alice_plmc_balance - post_alice_plmc_balance, expected_plmc_fee + expected_plmc_tip); + assert_eq!( + post_blockchain_operation_treasury_usdt_balance, prev_blockchain_operation_treasury_usdt_balance + ); + assert_eq!( + post_blockchain_operation_treasury_plmc_balance - prev_blockchain_operation_treasury_plmc_balance, + expected_plmc_fee + ); + assert_eq!(post_block_author_usdt_balance, prev_block_author_usdt_balance); + assert_eq!(post_block_author_plmc_balance - prev_block_author_plmc_balance, expected_plmc_tip); + }); } diff --git a/nodes/parachain/src/chain_spec/common.rs b/nodes/parachain/src/chain_spec/common.rs index 6959af592..a1b5c383e 100644 --- a/nodes/parachain/src/chain_spec/common.rs +++ b/nodes/parachain/src/chain_spec/common.rs @@ -1,12 +1,10 @@ use crate::chain_spec::{get_account_id_from_seed, Extensions}; use cumulus_primitives_core::ParaId; -use frame_support::traits::fungible::Inspect; #[cfg(not(feature = "runtime-benchmarks"))] use itertools::Itertools; #[cfg(not(feature = "runtime-benchmarks"))] use polimec_runtime::MinCandidateStk; use polimec_runtime::{ - pallet_parachain_staking, pallet_parachain_staking::{ inflation::{perbill_annual_to_perbill_round, BLOCKS_PER_YEAR}, InflationInfo, Range, diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index 36bcbab1a..5daf1dec0 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -237,6 +237,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "pallet-asset-tx-payment/runtime-benchmarks" ] try-runtime = [ diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 04ce16cc2..14a2242f0 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -26,7 +26,6 @@ use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, genesis_builder_helper::{build_state, get_preset}, - instances::Instance1, ord_parameter_types, parameter_types, traits::{ fungible::{Credit, HoldConsideration, Inspect}, @@ -94,8 +93,7 @@ use polimec_common::{ProvideAssetPrice, USD_UNIT}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; use sp_runtime::{ - traits::{DispatchInfoOf, PostDispatchInfoOf}, - transaction_validity::{InvalidTransaction, TransactionValidityError}, + transaction_validity::{InvalidTransaction}, }; #[cfg(feature = "runtime-benchmarks")] mod benchmark_helpers; @@ -152,10 +150,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, // TODO: Use parity's implementation once // https://github.com/paritytech/polkadot-sdk/pull/3993 is available. - pallet_dispenser::extensions::SkipCheckIfFeeless< - Runtime, - pallet_transaction_payment::ChargeTransactionPayment, - >, + pallet_dispenser::extensions::SkipCheckIfFeeless>, frame_metadata_hash_extension::CheckMetadataHash, ); @@ -906,9 +901,9 @@ where frame_system::CheckWeight::::new(), // TODO: Use parity's implementation once // https://github.com/paritytech/polkadot-sdk/pull/3993 is available. - pallet_dispenser::extensions::SkipCheckIfFeeless::from( - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - ), + pallet_dispenser::extensions::SkipCheckIfFeeless::from(pallet_asset_tx_payment::ChargeAssetTxPayment::< + Runtime, + >::from(tip, None)), frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = generic::SignedPayload::new(call, extra) diff --git a/runtimes/shared-configuration/Cargo.toml b/runtimes/shared-configuration/Cargo.toml index b433e69ea..17ffa3dcc 100644 --- a/runtimes/shared-configuration/Cargo.toml +++ b/runtimes/shared-configuration/Cargo.toml @@ -64,6 +64,7 @@ std = [ "sp-arithmetic/std", "sp-runtime/std", "sp-std/std", + "pallet-asset-tx-payment/std" ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", @@ -76,6 +77,7 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polimec-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-asset-tx-payment/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -89,5 +91,6 @@ try-runtime = [ "pallet-treasury?/try-runtime", "polimec-common/try-runtime", "sp-runtime/try-runtime", + "pallet-asset-tx-payment/try-runtime" ] development-settings = [] diff --git a/runtimes/shared-configuration/src/fee.rs b/runtimes/shared-configuration/src/fee.rs index 4ec4758d7..5e6b54042 100644 --- a/runtimes/shared-configuration/src/fee.rs +++ b/runtimes/shared-configuration/src/fee.rs @@ -242,7 +242,7 @@ where // Calculate how much refund we should return. let (final_fee, refund) = paid.split(converted_fee); // Split the tip from the fee - let (final_fee_minus_tip, final_tip) = final_fee.split(converted_tip); + let (final_tip, final_fee_minus_tip) = final_fee.split(converted_tip); let _ = >::resolve(who, refund);