diff --git a/Cargo.lock b/Cargo.lock index a17c304ad..92d0d838f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8829,6 +8829,7 @@ dependencies = [ "log", "on-slash-vesting", "orml-oracle", + "pallet-asset-tx-payment", "pallet-assets", "pallet-aura", "pallet-authorship", @@ -13194,6 +13195,7 @@ dependencies = [ "frame-support", "frame-system", "orml-traits", + "pallet-asset-tx-payment", "pallet-authorship", "pallet-balances", "pallet-funding", diff --git a/Cargo.toml b/Cargo.toml index 84c3a5fbd..9f8df9ab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,7 +158,6 @@ pallet-assets = { version = "36.0.0", default-features = false } pallet-authorship = { version = "35.0.0", default-features = false } pallet-session = { version = "35.0.0", default-features = false } pallet-timestamp = { version = "34.0.0", default-features = false } -pallet-asset-tx-payment = { version = "35.0.0", default-features = false } pallet-collective = { version = "35.0.0", default-features = false } pallet-scheduler = { version = "36.0.0", default-features = false } pallet-sudo = { version = "35.0.0", default-features = false } @@ -175,6 +174,7 @@ pallet-vesting = { version = "35.0.0", default-features = false } pallet-staking = { version = "35.0.0", default-features = false } pallet-proxy = { version = "35.0.0", default-features = false } pallet-identity = { version = "35.0.0", default-features = false } +pallet-asset-tx-payment = { version = "35.0.0", default-features = false } # Polkadot (with default disabled) pallet-xcm = { version = "14.0.0", default-features = false } diff --git a/integration-tests/src/tests/mod.rs b/integration-tests/src/tests/mod.rs index 26c716055..48af78f42 100644 --- a/integration-tests/src/tests/mod.rs +++ b/integration-tests/src/tests/mod.rs @@ -23,5 +23,6 @@ mod governance; mod oracle; mod otm_edge_cases; mod reserve_backed_transfers; +mod transaction_payment; mod vest; mod xcm_config; diff --git a/integration-tests/src/tests/transaction_payment.rs b/integration-tests/src/tests/transaction_payment.rs new file mode 100644 index 000000000..67d03a936 --- /dev/null +++ b/integration-tests/src/tests/transaction_payment.rs @@ -0,0 +1,4 @@ +#[test] +fn fee_paid_with_foreign_assets() { + todo!(); +} diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index 35e7c95e3..87a8f5f80 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -77,6 +77,7 @@ std = [ "pallet-balances/std", "pallet-insecure-randomness-collective-flip/std", "pallet-linear-release/std", + "pallet-proxy-bonding/std", "pallet-timestamp/std", "pallet-xcm/std", "parachains-common/std", @@ -96,7 +97,6 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", - "pallet-proxy-bonding/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -107,6 +107,7 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-linear-release/runtime-benchmarks", + "pallet-proxy-bonding/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", @@ -117,7 +118,6 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", - "pallet-proxy-bonding/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -126,11 +126,11 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", "pallet-linear-release/try-runtime", + "pallet-proxy-bonding/try-runtime", "pallet-timestamp/try-runtime", "pallet-xcm/try-runtime", "polimec-common-test-utils?/try-runtime", "polimec-common/try-runtime", "sp-runtime/try-runtime", - "pallet-proxy-bonding/try-runtime" ] on-chain-release-build = [] diff --git a/pallets/proxy-bonding/Cargo.toml b/pallets/proxy-bonding/Cargo.toml index b025c7855..6050679e3 100644 --- a/pallets/proxy-bonding/Cargo.toml +++ b/pallets/proxy-bonding/Cargo.toml @@ -31,41 +31,41 @@ pallet-assets.workspace = true [features] -default = ["std"] +default = [ "std" ] std = [ - "frame-system/std", + "frame-benchmarking?/std", "frame-support/std", - "sp-runtime/std", - "polimec-common/std", + "frame-system/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-linear-release/std", "parity-scale-codec/std", + "polimec-common/std", "scale-info/std", - "frame-benchmarking?/std", - + "serde/std", + "sp-core/std", "sp-io/std", - "pallet-linear-release/std", - "pallet-balances/std", - "pallet-assets/std", - "serde/std" + "sp-runtime/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", - "polimec-common/try-runtime", - "pallet-linear-release/try-runtime", "pallet-assets/try-runtime", - "pallet-balances/try-runtime" + "pallet-balances/try-runtime", + "pallet-linear-release/try-runtime", + "polimec-common/try-runtime", + "sp-runtime/try-runtime", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "polimec-common/runtime-benchmarks", - "pallet-linear-release/runtime-benchmarks", "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks" + "pallet-balances/runtime-benchmarks", + "pallet-linear-release/runtime-benchmarks", + "polimec-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index 388523a5a..36bcbab1a 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -83,6 +83,7 @@ sp-transaction-pool.workspace = true sp-version.workspace = true sp-genesis-builder.workspace = true frame-metadata-hash-extension.workspace = true +pallet-asset-tx-payment.workspace = true # Polkadot pallet-xcm.workspace = true @@ -135,6 +136,7 @@ std = [ "log/std", "on-slash-vesting/std", "orml-oracle/std", + "pallet-asset-tx-payment/std", "pallet-assets/std", "pallet-aura/std", "pallet-authorship/std", @@ -153,6 +155,7 @@ std = [ "pallet-oracle-ocw/std", "pallet-parachain-staking/std", "pallet-preimage/std", + "pallet-proxy-bonding/std", "pallet-proxy/std", "pallet-scheduler/std", "pallet-session/std", @@ -175,7 +178,7 @@ std = [ "sp-block-builder/std", "sp-consensus-aura/std", "sp-core/std", -# "sp-debug-derive/std", + # "sp-debug-derive/std", "sp-genesis-builder/std", "sp-inherents/std", "sp-offchain/std", @@ -188,7 +191,6 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", - "pallet-proxy-bonding/std" ] runtime-benchmarks = [ @@ -219,6 +221,7 @@ runtime-benchmarks = [ "pallet-oracle-ocw/runtime-benchmarks", "pallet-parachain-staking/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", + "pallet-proxy-bonding/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -234,7 +237,6 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", - "pallet-proxy-bonding/runtime-benchmarks" ] try-runtime = [ @@ -247,6 +249,7 @@ try-runtime = [ "frame-system/try-runtime", "frame-try-runtime/try-runtime", "orml-oracle/try-runtime", + "pallet-asset-tx-payment/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", @@ -265,6 +268,7 @@ try-runtime = [ "pallet-oracle-ocw/try-runtime", "pallet-parachain-staking/try-runtime", "pallet-preimage/try-runtime", + "pallet-proxy-bonding/try-runtime", "pallet-proxy/try-runtime", "pallet-scheduler/try-runtime", "pallet-session/try-runtime", @@ -279,7 +283,6 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "shared-configuration/try-runtime", "sp-runtime/try-runtime", - "pallet-proxy-bonding/try-runtime" ] # A feature that should be enabled when the runtime should be built for on-chain diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index d12ffb134..04ce16cc2 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -26,10 +26,12 @@ 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}, - tokens::{self, PayFromAccount, UnityAssetBalanceConversion}, + fungibles, + tokens::{self, ConversionToAssetBalance, PayFromAccount, UnityAssetBalanceConversion}, AsEnsureOriginWithArg, ConstU32, Contains, EitherOfDiverse, InstanceFilter, LinearStoragePrice, PrivilegeCmp, TransformOrigin, }, @@ -41,9 +43,10 @@ use pallet_aura::Authorities; use pallet_democracy::GetElectorate; use pallet_funding::{ runtime_api::ProjectParticipationIds, types::AcceptedFundingAsset, BidInfoOf, ContributionInfoOf, DaysToBlocks, - EvaluationInfoOf, ProjectDetailsOf, ProjectId, ProjectMetadataOf, + EvaluationInfoOf, PriceProviderOf, ProjectDetailsOf, ProjectId, ProjectMetadataOf, }; use parachains_common::{ + impls::AssetsToBlockAuthor, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AssetIdForTrustBackedAssets as AssetId, }; @@ -60,7 +63,7 @@ use sp_runtime::{ IdentifyAccount, IdentityLookup, OpaqueKeys, Verify, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedU128, MultiSignature, SaturatedConversion, + ApplyExtrinsicResult, FixedPointNumber, FixedU128, MultiSignature, SaturatedConversion, }; use sp_std::{cmp::Ordering, prelude::*}; use sp_version::RuntimeVersion; @@ -87,10 +90,13 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use sp_version::NativeVersion; use crate::xcm_config::PriceForSiblingParachainDelivery; -use polimec_common::USD_UNIT; +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}, +}; #[cfg(feature = "runtime-benchmarks")] mod benchmark_helpers; mod custom_migrations; @@ -1154,6 +1160,34 @@ impl pallet_dispenser::Config for Runtime { type WhitelistedPolicy = DispenserWhitelistedPolicy; } +pub struct PLMCToFundingAssetBalance; +impl ConversionToAssetBalance for PLMCToFundingAssetBalance { + type Error = InvalidTransaction; + + fn to_asset_balance(plmc_balance: Balance, asset_id: AssetId) -> Result { + let plmc_price = + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) + .ok_or(InvalidTransaction::Payment)?; + let funding_asset_decimals = >::decimals(asset_id); + let funding_asset_price = + >::get_decimals_aware_price(asset_id, USD_DECIMALS, funding_asset_decimals) + .ok_or(InvalidTransaction::Payment)?; + let usd_balance = plmc_price.saturating_mul_int(plmc_balance); + let funding_asset_balance = + funding_asset_price.reciprocal().ok_or(InvalidTransaction::Payment)?.saturating_mul_int(usd_balance); + Ok(funding_asset_balance) + } +} +impl pallet_asset_tx_payment::Config for Runtime { + type Fungibles = ForeignAssets; + type OnChargeAssetTransaction = TxFeeFungiblesAdapter< + PLMCToFundingAssetBalance, + CreditFungiblesToAccount, + AssetsToBlockAuthor, + >; + type RuntimeEvent = RuntimeEvent; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -1176,6 +1210,7 @@ construct_runtime!( ContributionTokens: pallet_assets:: = 13, ForeignAssets: pallet_assets:: = 14, Dispenser: pallet_dispenser = 15, + AssetTransactionPayment: pallet_asset_tx_payment = 16, // Collator support. the order of these 5 are important and shall not change. Authorship: pallet_authorship::{Pallet, Storage} = 20, diff --git a/runtimes/shared-configuration/Cargo.toml b/runtimes/shared-configuration/Cargo.toml index e370204e1..b433e69ea 100644 --- a/runtimes/shared-configuration/Cargo.toml +++ b/runtimes/shared-configuration/Cargo.toml @@ -40,6 +40,7 @@ pallet-authorship.workspace = true pallet-parachain-staking.workspace = true pallet-oracle-ocw.workspace = true pallet-treasury = {workspace = true, optional = true} +pallet-asset-tx-payment.workspace = true [features] default = [ "std" ] diff --git a/runtimes/shared-configuration/src/fee.rs b/runtimes/shared-configuration/src/fee.rs index 62ca622b9..4ec4758d7 100644 --- a/runtimes/shared-configuration/src/fee.rs +++ b/runtimes/shared-configuration/src/fee.rs @@ -13,15 +13,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - +extern crate alloc; use crate::{currency::MILLI_PLMC, Balance, StakingPalletId}; use frame_support::{ ord_parameter_types, - pallet_prelude::Weight, + pallet_prelude::{MaybeSerializeDeserialize, Weight}, parameter_types, sp_runtime::traits::AccountIdConversion, traits::{ fungible::{Balanced, Credit}, + fungibles, + fungibles::Inspect, + tokens::{ + ConversionToAssetBalance, Fortitude::Polite, Precision::Exact, Preservation::Protect, WithdrawConsequence, + }, Imbalance, OnUnbalanced, }, weights::{ @@ -29,10 +34,18 @@ use frame_support::{ WeightToFeePolynomial, }, }; -use parachains_common::{AccountId, SLOT_DURATION}; -use scale_info::prelude::vec; +use pallet_asset_tx_payment::{HandleCredit, OnChargeAssetTransaction}; +use pallet_transaction_payment::OnChargeTransaction; +use parachains_common::{impls::AccountIdOf, AccountId, SLOT_DURATION}; +use parity_scale_codec::FullCodec; +use scale_info::{prelude::vec, TypeInfo}; use smallvec::smallvec; use sp_arithmetic::Perbill; +use sp_runtime::{ + traits::{DispatchInfoOf, Get, One, PostDispatchInfoOf, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, +}; +use core::{fmt::Debug, marker::PhantomData}; #[allow(clippy::module_name_repetitions)] pub struct WeightToFee; @@ -100,7 +113,7 @@ ord_parameter_types! { } /// Logic for the author to get a portion of fees. -pub struct ToAuthor(sp_std::marker::PhantomData); +pub struct ToAuthor(PhantomData); impl OnUnbalanced>> for ToAuthor where R: pallet_balances::Config + pallet_authorship::Config, @@ -115,7 +128,7 @@ where } /// Implementation of `OnUnbalanced` that deposits the fees into the "Blockchain Operation Treasury" for later payout. -pub struct ToStakingPot(sp_std::marker::PhantomData); +pub struct ToStakingPot(PhantomData); impl OnUnbalanced>> for ToStakingPot where R: pallet_balances::Config + pallet_parachain_staking::Config, @@ -128,7 +141,7 @@ where } } -pub struct DealWithFees(sp_std::marker::PhantomData); +pub struct DealWithFees(PhantomData); impl OnUnbalanced>> for DealWithFees where R: pallet_balances::Config + pallet_authorship::Config + pallet_parachain_staking::Config, @@ -148,3 +161,104 @@ where } } } + +type BalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; +type AssetIdOf = <::Fungibles as Inspect>>::AssetId; +type AssetBalanceOf = + <::Fungibles as Inspect<::AccountId>>::Balance; + +/// Implements the asset transaction for a balance to asset converter (implementing +/// [`ConversionToAssetBalance`]) and 2 credit handlers (implementing [`HandleCredit`]). +/// +/// First handler does the fee, second the tip. +pub struct TxFeeFungiblesAdapter( + PhantomData<(Converter, FeeCreditor, TipCreditor)>, +); + +/// Default implementation for a runtime instantiating this pallet, a balance to asset converter and +/// a credit handler. +impl OnChargeAssetTransaction + for TxFeeFungiblesAdapter +where + Runtime: pallet_asset_tx_payment::Config, + Converter: ConversionToAssetBalance, AssetIdOf, AssetBalanceOf>, + FeeCreditor: HandleCredit, + TipCreditor: HandleCredit, + AssetIdOf: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, +{ + type AssetId = AssetIdOf; + type Balance = BalanceOf; + type LiquidityInfo = fungibles::Credit; + + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &Runtime::AccountId, + _call: &Runtime::RuntimeCall, + _info: &DispatchInfoOf, + asset_id: Self::AssetId, + fee: Self::Balance, + _tip: Self::Balance, + ) -> Result { + // We don't know the precision of the underlying asset. Because the converted fee could be + // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum + // fee. + let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; + let converted_fee = Converter::to_asset_balance(fee, asset_id) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? + .max(min_converted_fee); + let can_withdraw = + >::can_withdraw(asset_id, who, converted_fee); + if can_withdraw != WithdrawConsequence::Success { + return Err(InvalidTransaction::Payment.into()) + } + >::withdraw( + asset_id, + who, + converted_fee, + Exact, + Protect, + Polite, + ) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) + } + + /// Note: The `corrected_fee` already includes the `tip`. + fn correct_and_deposit_fee( + who: &Runtime::AccountId, + _dispatch_info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + tip: Self::Balance, + paid: Self::LiquidityInfo, + ) -> Result<(AssetBalanceOf, AssetBalanceOf), TransactionValidityError> { + let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() }; + // Convert the corrected fee and tip into the asset used for payment. + let converted_fee = Converter::to_asset_balance(corrected_fee, paid.asset()) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })? + .max(min_converted_fee); + let converted_tip = Converter::to_asset_balance(tip, paid.asset()) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?; + + // 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 _ = >::resolve(who, refund); + + FeeCreditor::handle_credit(final_fee_minus_tip); + TipCreditor::handle_credit(final_tip); + + Ok((converted_fee, converted_tip)) + } +} + +pub struct CreditFungiblesToAccount(PhantomData<(AccountId, Assets, Account)>); +impl, Account: Get> + HandleCredit for CreditFungiblesToAccount +{ + fn handle_credit(credit: fungibles::Credit) { + let payee: AccountId = Account::get(); + let _ = >::resolve(&payee, credit); + } +}