Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify the currency for the execution fee #461

Merged
merged 25 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7bdc7ae
Specify the currency for the execution fee
imstar15 Nov 2, 2023
b30e2b5
Remove UnspportedFeePayment error
imstar15 Nov 3, 2023
9c7e873
Merge branch 'master' into specify-currency-for-execution-fee
imstar15 Nov 6, 2023
1be03c5
Deducting foreign tokens as an execution fee
imstar15 Nov 7, 2023
f5a3d21
In the normal flow, different asset types should use different XCM in…
imstar15 Nov 8, 2023
f5bddd9
Make sure that the execution fee is the token of this chain or the to…
imstar15 Nov 9, 2023
28c77e1
Optimize the logic of can_pay_fee function
imstar15 Nov 9, 2023
c2260c4
fixup: auto format Rust code
Nov 9, 2023
5f9d150
Change the type of schedule_fee to FeePayment
imstar15 Nov 10, 2023
2d8387c
Fix ensure_supported_execution_fee_location function
imstar15 Nov 10, 2023
bab747c
Replace unwrap with ok_or
imstar15 Nov 12, 2023
86da244
fixup: auto format Rust code
Nov 12, 2023
3f0ebc1
Modify the code to pass the old test cases
imstar15 Nov 13, 2023
955fd18
Fix tests
imstar15 Nov 14, 2023
0ad89ef
Add tests for scheduling xcmp task with multi currency
imstar15 Nov 14, 2023
8a42d8b
Fix can_pay_fee function
imstar15 Nov 15, 2023
27f51dd
Modify local_xcm in get_local_currency_instructions
imstar15 Nov 15, 2023
3e63eda
fixup: auto format Rust code
Nov 15, 2023
cea3072
Fix transact_in_local_chain_works test
imstar15 Nov 16, 2023
a6fa680
Add comment for ensure_supported_execution_fee_location
imstar15 Nov 16, 2023
7c2f3e2
Restructure get_local_currency_instructions function
imstar15 Nov 16, 2023
3769dc7
Add tests
imstar15 Nov 16, 2023
6c82be9
Add comments for withdrawing schedule_fee and exeuction_fee
imstar15 Nov 17, 2023
91bb6b5
Rename spy to has_callback_run
imstar15 Nov 17, 2023
6e7f5c3
Add pay_checked_fees_for test for foreign schedule fee
imstar15 Nov 17, 2023
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pallets/automation-price/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ where

let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();

match T::MultiCurrency::withdraw(currency_id, &self.owner, fee) {
match T::MultiCurrency::withdraw(currency_id.into(), &self.owner, fee) {
Ok(_) => {
TR::take_revenue(MultiAsset {
id: AssetId::Concrete(self.schedule_fee_location),
Expand All @@ -112,6 +111,7 @@ where

if self.execution_fee_amount > MultiBalanceOf::<T>::zero() {
T::XcmpTransactor::pay_xcm_fee(
currency_id,
self.owner.clone(),
self.execution_fee_amount.saturated_into(),
)?;
Expand Down
6 changes: 5 additions & 1 deletion pallets/automation-price/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,11 @@ where
Ok(())
}

fn pay_xcm_fee(_: T::AccountId, _: u128) -> Result<(), sp_runtime::DispatchError> {
fn pay_xcm_fee(
_: CurrencyId,
_: T::AccountId,
_: u128,
) -> Result<(), sp_runtime::DispatchError> {
Ok(())
}
}
Expand Down
158 changes: 115 additions & 43 deletions pallets/automation-time/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/// ! Traits and default implementation for paying execution fees.
use crate::{AccountOf, Action, ActionOf, Config, Error, MultiBalanceOf, Pallet};

use frame_support::traits::Get;
use orml_traits::MultiCurrency;
use pallet_xcmp_handler::{InstructionSequence, XcmpTransactor};
use sp_runtime::{
Expand All @@ -39,11 +40,17 @@ pub trait HandleFees<T: Config> {
) -> Result<R, DispatchError>;
}

#[derive(Clone)]
pub struct FeePayment<T: Config> {
pub asset_location: MultiLocation,
pub amount: MultiBalanceOf<T>,
pub is_local: bool,
}

pub struct FeeHandler<T: Config, TR> {
owner: T::AccountId,
pub schedule_fee_location: MultiLocation,
pub schedule_fee_amount: MultiBalanceOf<T>,
pub execution_fee_amount: MultiBalanceOf<T>,
pub schedule_fee: FeePayment<T>,
pub execution_fee: Option<FeePayment<T>>,
_phantom_data: PhantomData<TR>,
}

Expand Down Expand Up @@ -71,59 +78,110 @@ where
T: Config,
TR: TakeRevenue,
{
/// Ensure the fee can be paid.
fn can_pay_fee(&self) -> Result<(), DispatchError> {
let fee = self.schedule_fee_amount.saturating_add(self.execution_fee_amount);

if fee.is_zero() {
fn ensure_can_withdraw(
&self,
asset_location: MultiLocation,
amount: MultiBalanceOf<T>,
) -> Result<(), DispatchError> {
if amount.is_zero() {
return Ok(())
}

// Manually check for ExistenceRequirement since MultiCurrency doesn't currently support it
let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();
let currency_id = T::CurrencyIdConvert::convert(asset_location)
.ok_or("IncoveribleMultilocation")?
.into();
let free_balance = T::MultiCurrency::free_balance(currency_id, &self.owner);
let min_balance = T::MultiCurrency::minimum_balance(currency_id);

free_balance
.checked_sub(&fee)
.ok_or(DispatchError::Token(BelowMinimum))?
.checked_sub(&T::MultiCurrency::minimum_balance(currency_id))
.checked_sub(&amount)
.and_then(|balance_minus_fee| balance_minus_fee.checked_sub(&min_balance))
.ok_or(DispatchError::Token(BelowMinimum))?;
T::MultiCurrency::ensure_can_withdraw(currency_id, &self.owner, fee)?;

T::MultiCurrency::ensure_can_withdraw(currency_id, &self.owner, amount)?;

Ok(())
}

/// Ensure the fee can be paid.
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
fn can_pay_fee(&self) -> Result<(), DispatchError> {
match &self.execution_fee {
Some(exec_fee) if exec_fee.is_local => {
// If the locations of schedule_fee and execution_fee are equal,
// we need to add the fees to check whether they are sufficient,
// otherwise check them separately.
let exec_fee_location = exec_fee
.asset_location
.reanchored(&T::SelfLocation::get(), T::UniversalLocation::get())
.map_err(|_| Error::<T>::CannotReanchor)?;

let schedule_fee_location = self
.schedule_fee
.asset_location
.reanchored(&T::SelfLocation::get(), T::UniversalLocation::get())
.map_err(|_| Error::<T>::CannotReanchor)?;

if exec_fee_location == schedule_fee_location {
let fee = self.schedule_fee.amount.saturating_add(exec_fee.amount);
Self::ensure_can_withdraw(self, exec_fee.asset_location, fee)?;
} else {
Self::ensure_can_withdraw(
self,
self.schedule_fee.asset_location,
self.schedule_fee.amount,
)?;
}
},
_ => {
Self::ensure_can_withdraw(
self,
self.schedule_fee.asset_location,
self.schedule_fee.amount,
)?;
},
}

Ok(())
}

/// Withdraw the fee.
fn withdraw_fee(&self) -> Result<(), DispatchError> {
let fee = self.schedule_fee_amount.saturating_add(self.execution_fee_amount);
log::debug!(target: "FeeHandler", "FeeHandler::withdraw_fee, self.schedule_fee.asset_location: {:?}, self.schedule_fee.amount: {:?}",
self.schedule_fee.asset_location, self.schedule_fee.amount);
// Withdraw schedule fee
if !self.schedule_fee.amount.is_zero() {
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee.asset_location)
.ok_or("InconvertibleMultilocation")?;

if fee.is_zero() {
return Ok(())
}
T::MultiCurrency::withdraw(currency_id.into(), &self.owner, self.schedule_fee.amount)
.map_err(|_| DispatchError::Token(BelowMinimum))?;

let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();
TR::take_revenue(MultiAsset {
id: AssetId::Concrete(self.schedule_fee.asset_location),
fun: Fungibility::Fungible(self.schedule_fee.amount.saturated_into()),
});
}

match T::MultiCurrency::withdraw(currency_id, &self.owner, fee) {
Ok(_) => {
TR::take_revenue(MultiAsset {
id: AssetId::Concrete(self.schedule_fee_location),
fun: Fungibility::Fungible(self.schedule_fee_amount.saturated_into()),
});
// Withdraw execution fee
if let Some(execution_fee) = &self.execution_fee {
if execution_fee.is_local {
log::debug!(target: "FeeHandler", "FeeHandler::withdraw_fee, self.execution_fee.asset_location: {:?}, self.execution_fee.amount: {:?}",
execution_fee.asset_location, execution_fee.amount);
let currency_id = T::CurrencyIdConvert::convert(execution_fee.asset_location)
.ok_or("InconvertibleMultilocation")?;

if self.execution_fee_amount > MultiBalanceOf::<T>::zero() {
let execution_fee_amount = execution_fee.amount;
if !execution_fee_amount.is_zero() {
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
T::XcmpTransactor::pay_xcm_fee(
currency_id,
self.owner.clone(),
self.execution_fee_amount.saturated_into(),
execution_fee_amount.saturated_into(),
)?;
}

Ok(())
},
Err(_) => Err(DispatchError::Token(BelowMinimum)),
}
}

Ok(())
}

/// Builds an instance of the struct
Expand All @@ -137,18 +195,32 @@ where
let schedule_fee_amount: u128 =
Pallet::<T>::calculate_schedule_fee_amount(action, executions)?.saturated_into();

let execution_fee_amount = match action.clone() {
Action::XCMP { execution_fee, instruction_sequence, .. }
if instruction_sequence == InstructionSequence::PayThroughSovereignAccount =>
execution_fee.amount.saturating_mul(executions.into()).saturated_into(),
_ => 0u32.saturated_into(),
let schedule_fee = FeePayment {
asset_location: schedule_fee_location,
amount: schedule_fee_amount.saturated_into(),
is_local: true,
};

let execution_fee = match action.clone() {
Action::XCMP { execution_fee, instruction_sequence, .. } => {
let location = MultiLocation::try_from(execution_fee.asset_location)
.map_err(|()| Error::<T>::BadVersion)?;
let amount =
execution_fee.amount.saturating_mul(executions.into()).saturated_into();
Some(FeePayment {
asset_location: location,
amount,
is_local: instruction_sequence ==
InstructionSequence::PayThroughSovereignAccount,
})
},
_ => None,
};

Ok(Self {
owner: owner.clone(),
schedule_fee_location,
schedule_fee_amount: schedule_fee_amount.saturated_into(),
execution_fee_amount,
schedule_fee,
execution_fee,
_phantom_data: Default::default(),
})
}
Expand Down
Loading
Loading