Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Asset Transaction Payment #488

Merged
merged 75 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
bee1ce0
use new pallet name based genesis config names
apopiak Jun 10, 2021
c0d13a9
use custom substrate and update polkadot
apopiak Jun 10, 2021
c9266c5
add initial asset-tx-payment pallet
apopiak Jun 10, 2021
7112a86
update cargo.toml
apopiak Jun 10, 2021
9297f6e
add (failing) tests
apopiak Jun 10, 2021
7dd9bea
dispatch Calls instead of using Pallet functions
apopiak Jun 10, 2021
63af3bb
fix fee-refund split
apopiak Jun 10, 2021
f8f838a
add test for transaction payment with tip
apopiak Jun 10, 2021
b31dc8c
update cargo.lock
apopiak Jun 10, 2021
642f8c9
update cargo.lock
apopiak Jun 11, 2021
27c65db
remove mint workaround and use Mutable trait
apopiak Jun 11, 2021
78f1b90
extract fee charging logic into OnChargeAssetTransaction trait
apopiak Jun 14, 2021
3097861
use asset-tx-payment in statemint runtime
apopiak Jun 14, 2021
b9b394e
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Jun 14, 2021
2c10e02
make extrinsics public
apopiak Jun 16, 2021
5f18e5b
make extrinsics public
apopiak Jun 16, 2021
7f36a58
use ChargeAssetIdOf type alias
apopiak Jun 16, 2021
0f29540
update deps
apopiak Jun 16, 2021
4332761
move back to AssetIdOf
apopiak Jun 16, 2021
c77215e
Merge remote-tracking branch 'origin/master' into apopiak/asset-tx-pa…
apopiak Jun 17, 2021
eb9ac14
remove extra rpc_http_threads
apopiak Jun 17, 2021
39e08eb
use different substrate branch
apopiak Jun 17, 2021
1138b90
Update pallets/asset-tx-payment/src/payment.rs
apopiak Jun 25, 2021
34a350d
Update pallets/asset-tx-payment/src/payment.rs
apopiak Jun 25, 2021
b657f59
remove overrides
apopiak Jul 8, 2021
65013c5
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Jul 8, 2021
22816c2
override substrate deps (again)
apopiak Jul 8, 2021
5c30e05
increment spec_version and transaction_version (because we change tra…
apopiak Jul 8, 2021
91afb68
Merge branch 'apopiak/asset-tx-payment' of github.com:paritytech/cumu…
apopiak Jul 8, 2021
0d4acc0
remove direct dependency on pallet-balances from asset-tx-payment
apopiak Jul 8, 2021
a5ecd03
remove Assets pallet visibility workaround
apopiak Jul 8, 2021
530d250
add docs and comments
apopiak Jul 8, 2021
a260e66
remove unused imports
apopiak Jul 8, 2021
2c8ad19
more docs
apopiak Jul 8, 2021
be52a66
add more debug asserts to document assumptions
apopiak Jul 9, 2021
850f999
add test for tx payment from account with only assets
apopiak Jul 9, 2021
1008e67
add test for missing asset case
apopiak Jul 9, 2021
95d4f42
extend test to cover non-sufficient assets
apopiak Jul 9, 2021
4e56a2c
add a test for Pays::No (refunded transaction)
apopiak Jul 9, 2021
86f1d94
add type alias comments
apopiak Jul 9, 2021
5e890f5
add more doc comments
apopiak Jul 9, 2021
1600514
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Sep 21, 2021
d91263b
add asset-tx-payment to statemine and westmint
apopiak Sep 21, 2021
9b7bb39
improve formatting
apopiak Sep 27, 2021
8c5fc82
update license headers
apopiak Sep 27, 2021
8db45aa
add default implementation of HandleCredit for ()
apopiak Sep 27, 2021
8c07bcd
update doc comments and format imports
apopiak Sep 27, 2021
d64f202
adjust Cargo.toml
apopiak Sep 27, 2021
1e650d1
update cargo.lock
apopiak Sep 27, 2021
749cdcf
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Sep 27, 2021
10c3ddf
cargo fmt
apopiak Sep 27, 2021
03f4e12
cargo fmt
apopiak Sep 27, 2021
24dc914
cargo fmt
apopiak Sep 27, 2021
b43da32
cargo +nightly fmt
apopiak Sep 27, 2021
a763672
add type alias for OnChargeTransaction
apopiak Sep 28, 2021
35e4098
cargo +nightly fmt
apopiak Sep 28, 2021
c2fefd5
convert ChargeAssetTxPayment from tuple struct to regular struct
apopiak Sep 28, 2021
d77ff7b
add more comments
apopiak Sep 28, 2021
58c6b7a
formatting
apopiak Sep 28, 2021
6474c34
adjust imports and comment
apopiak Sep 28, 2021
b9382e9
cargo +nightly fmt
apopiak Sep 28, 2021
8638ed8
reformat comment
apopiak Oct 4, 2021
a1d770a
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Oct 5, 2021
7bcac89
use ChargeTransactionPayment's own get_priority + update Substrate
apopiak Oct 5, 2021
acea23e
update Substrate and Polkadot
apopiak Oct 5, 2021
3e7b9c5
Merge branch 'apopiak/transaction-payment-update' into apopiak/asset-…
apopiak Oct 5, 2021
359ee56
cargo fmt
apopiak Oct 5, 2021
1c32551
cargo fmt
apopiak Oct 5, 2021
382120c
add OperationalFeeMultiplier to asset tx payment tests
apopiak Oct 5, 2021
96fd61d
Apply suggestions from code review
apopiak Oct 7, 2021
99349b9
add doc links
apopiak Oct 7, 2021
aef723e
charge a minimum converted asset fee of 1 if the input fee is greater…
apopiak Oct 8, 2021
8fd99f7
cargo +nightly fmt
apopiak Oct 8, 2021
35d13e4
Merge branch 'master' of github.com:paritytech/cumulus into apopiak/a…
apopiak Oct 11, 2021
a413579
bump spec and transaction version
apopiak Oct 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
503 changes: 265 additions & 238 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"client/network",
"client/pov-recovery",
"client/service",
"pallets/asset-tx-payment",
"pallets/aura-ext",
"pallets/collator-selection",
"pallets/dmp-queue",
Expand Down
51 changes: 51 additions & 0 deletions pallets/asset-tx-payment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[package]
name = "pallet-asset-tx-payment"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/cumulus/"
description = "pallet to manage transaction payments in assets"
readme = "README.md"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-authorship = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }

# Other dependencies
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", optional = true }

[dev-dependencies]
smallvec = "1.4.1"
serde_json = "1.0.41"
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-storage = { git = "https://github.com/paritytech/substrate", branch = "master" }

[features]
default = ["std"]
std = [
"serde",
"codec/std",
"sp-std/std",
"sp-runtime/std",
"frame-support/std",
"frame-system/std",
"sp-io/std",
"sp-core/std",
"pallet-transaction-payment/std",
]
try-runtime = ["frame-support/try-runtime"]
293 changes: 293 additions & 0 deletions pallets/asset-tx-payment/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # Asset Transaction Payment Pallet
//!
//! This pallet allows runtimes that include it to pay for transactions in assets other than the
//! main token of the chain.
//!
//! ## Overview
//! It does this by extending transactions to include an optional `AssetId` that specifies the asset
//! to be used for payment (defaulting to the native token on `None`). It expects an
//! `OnChargeAssetTransaction` implementation analogously to `pallet-transaction-payment`. The
//! included `FungiblesAdapter` (implementing `OnChargeAssetTransaction`) determines the fee amount
//! by converting the fee calculated by `pallet-transaction-payment` into the desired asset.
//!
//! ## Integration
//! This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means
//! you should include both pallets in your `construct_runtime` macro, but only include this
//! pallet's `SignedExtension` (`ChargeAssetTxPayment`).
apopiak marked this conversation as resolved.
Show resolved Hide resolved

#![cfg_attr(not(feature = "std"), no_std)]

use sp_std::prelude::*;

use codec::{Decode, Encode};
use frame_support::{
dispatch::DispatchResult,
traits::{
tokens::{
fungibles::{Balanced, CreditOf, Inspect},
WithdrawConsequence,
},
IsType,
},
weights::{DispatchInfo, PostDispatchInfo},
DefaultNoBound,
};
use pallet_transaction_payment::OnChargeTransaction;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero},
transaction_validity::{
InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
},
FixedPointOperand,
};

#[cfg(test)]
mod tests;

mod payment;
pub use payment::*;

// Type aliases used for interaction with `OnChargeTransaction`.
pub(crate) type OnChargeTransactionOf<T> =
<T as pallet_transaction_payment::Config>::OnChargeTransaction;
// Balance type alias.
pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
// Liquity info type alias.
pub(crate) type LiquidityInfoOf<T> =
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;

// Type alias used for interaction with fungibles (assets).
// Balance type alias.
pub(crate) type AssetBalanceOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
/// Asset id type alias.
pub(crate) type AssetIdOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;

// Type aliases used for interaction with `OnChargeAssetTransaction`.
// Balance type alias.
pub(crate) type ChargeAssetBalanceOf<T> =
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::Balance;
// Asset id type alias.
pub(crate) type ChargeAssetIdOf<T> =
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::AssetId;
// Liquity info type alias.
pub(crate) type ChargeAssetLiquidityOf<T> =
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::LiquidityInfo;

/// Used to pass the initial payment info from pre- to post-dispatch.
#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
pub enum InitialPayment<T: Config> {
/// No initial fee was payed.
Nothing,
/// The initial fee was payed in the native currency.
Native(LiquidityInfoOf<T>),
/// The initial fee was payed in an asset.
Asset(CreditOf<T::AccountId, T::Fungibles>),
}

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[pallet::config]
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
/// The fungibles instance used to pay for transactions in assets.
type Fungibles: Balanced<Self::AccountId>;
/// The actual transaction charging logic that charges the fees.
type OnChargeAssetTransaction: OnChargeAssetTransaction<Self>;
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::call]
impl<T: Config> Pallet<T> {}
}

/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue. Allows paying via both `Currency` as well as `fungibles::Balanced`.
///
/// Wraps the transaction logic in `pallet_transaction_payment` and extends it with assets.
/// An asset id of `None` falls back to the underlying transaction payment via the native currency.
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeAssetTxPayment<T: Config> {
#[codec(compact)]
tip: BalanceOf<T>,
asset_id: Option<ChargeAssetIdOf<T>>,
}

impl<T: Config> ChargeAssetTxPayment<T>
where
T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
BalanceOf<T>: Send + Sync + FixedPointOperand + IsType<ChargeAssetBalanceOf<T>>,
ChargeAssetIdOf<T>: Send + Sync,
CreditOf<T::AccountId, T::Fungibles>: IsType<ChargeAssetLiquidityOf<T>>,
{
/// utility constructor. Used only in client/factory code.
pub fn from(tip: BalanceOf<T>, asset_id: Option<ChargeAssetIdOf<T>>) -> Self {
Self { tip, asset_id }
}

/// Fee withdrawal logic that dispatches to either `OnChargeAssetTransaction` or `OnChargeTransaction`.
fn withdraw_fee(
&self,
who: &T::AccountId,
call: &T::Call,
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, self.tip);
debug_assert!(self.tip <= fee, "tip should be included in the computed fee");
if fee.is_zero() {
Ok((fee, InitialPayment::Nothing))
} else if let Some(asset_id) = self.asset_id {
T::OnChargeAssetTransaction::withdraw_fee(
who,
call,
info,
asset_id,
fee.into(),
self.tip.into(),
)
.map(|i| (fee, InitialPayment::Asset(i.into())))
} else {
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
who, call, info, fee, self.tip,
)
.map(|i| (fee, InitialPayment::Native(i)))
.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
}
}
}

impl<T: Config> sp_std::fmt::Debug for ChargeAssetTxPayment<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "ChargeAssetTxPayment<{:?}, {:?}>", self.tip, self.asset_id.encode())
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}

impl<T: Config> SignedExtension for ChargeAssetTxPayment<T>
where
T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand + IsType<ChargeAssetBalanceOf<T>>,
ChargeAssetIdOf<T>: Send + Sync,
CreditOf<T::AccountId, T::Fungibles>: IsType<ChargeAssetLiquidityOf<T>>,
{
const IDENTIFIER: &'static str = "ChargeAssetTxPayment";
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = ();
type Pre = (
// tip
BalanceOf<T>,
// who paid the fee
Self::AccountId,
// imbalance resulting from withdrawing the fee
InitialPayment<T>,
);
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
Ok(())
}

fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
use pallet_transaction_payment::ChargeTransactionPayment;
let (fee, _) = self.withdraw_fee(who, call, info, len)?;
let priority = ChargeTransactionPayment::<T>::get_priority(info, len, self.tip, fee);
Ok(ValidTransaction { priority, ..Default::default() })
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?;
Ok((self.tip, who.clone(), initial_payment))
}

fn post_dispatch(
pre: Self::Pre,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
_result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
let (tip, who, initial_payment) = pre;
let actual_fee = pallet_transaction_payment::Pallet::<T>::compute_actual_fee(
len as u32, info, post_info, tip,
);
match initial_payment {
InitialPayment::Native(already_withdrawn) => {
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::correct_and_deposit_fee(
&who,
info,
post_info,
actual_fee,
tip,
already_withdrawn,
)?;
},
InitialPayment::Asset(already_withdrawn) => {
T::OnChargeAssetTransaction::correct_and_deposit_fee(
&who,
info,
post_info,
actual_fee.into(),
tip.into(),
already_withdrawn.into(),
)?;
},
InitialPayment::Nothing => {
debug_assert!(
actual_fee.is_zero(),
"actual fee should be zero if initial fee was zero."
);
debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero.");
},
}

Ok(())
}
}
Loading