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 8 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
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
123 changes: 85 additions & 38 deletions pallets/automation-time/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ 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_deduction: bool,
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct FeeHandler<T: Config, TR> {
owner: T::AccountId,
pub schedule_fee_location: MultiLocation,
pub schedule_fee_amount: MultiBalanceOf<T>,
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
pub execution_fee_amount: MultiBalanceOf<T>,
pub execution_fee: Option<FeePayment<T>>,
_phantom_data: PhantomData<TR>,
}

Expand Down Expand Up @@ -71,59 +78,95 @@ 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> {
if let Some(exec_fee) = &self.execution_fee {
// 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.
if exec_fee.is_local_deduction && exec_fee.asset_location == self.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_location,
self.schedule_fee_amount,
)?;
Self::ensure_can_withdraw(self, exec_fee.asset_location, exec_fee.amount)?;
}
}

Ok(())
}

/// Withdraw the fee.
fn withdraw_fee(&self) -> Result<(), DispatchError> {
let fee = self.schedule_fee_amount.saturating_add(self.execution_fee_amount);
// Withdraw schedule fee
if !self.schedule_fee_amount.is_zero() {
let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_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))?;

TR::take_revenue(MultiAsset {
id: AssetId::Concrete(self.schedule_fee_location),
fun: Fungibility::Fungible(self.schedule_fee_amount.saturated_into()),
});
}

let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();
// Withdraw execution fee
if let Some(execution_fee) = &self.execution_fee {
if execution_fee.is_local_deduction {
let currency_id = T::CurrencyIdConvert::convert(execution_fee.asset_location)
.ok_or("InconvertibleMultilocation")?;

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()),
});
let execution_fee_amount = execution_fee.amount;
if !execution_fee_amount.is_zero() {
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
// T::MultiCurrency::withdraw(
// currency_id.into(),
// &self.owner,
// execution_fee_amount,
// )
// .map_err(|_| DispatchError::Token(BelowMinimum))?;

if self.execution_fee_amount > MultiBalanceOf::<T>::zero() {
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 +180,22 @@ 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 execution_fee = match action.clone() {
Action::XCMP { execution_fee, .. } => {
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_deduction: true })
},
_ => None,
};

Ok(Self {
owner: owner.clone(),
schedule_fee_location,
schedule_fee_amount: schedule_fee_amount.saturated_into(),
execution_fee_amount,
execution_fee,
_phantom_data: Default::default(),
})
}
Expand Down
68 changes: 45 additions & 23 deletions pallets/automation-time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ use frame_support::{
weights::constants::WEIGHT_REF_TIME_PER_SECOND,
};
use frame_system::pallet_prelude::*;
use orml_traits::{FixedConversionRateProvider, MultiCurrency};
use orml_traits::{
location::{Parse, Reserve},
FixedConversionRateProvider, MultiCurrency,
};
use pallet_parachain_staking::DelegatorActions;
use pallet_timestamp::{self as timestamp};
pub use pallet_xcmp_handler::InstructionSequence;
Expand Down Expand Up @@ -189,14 +192,19 @@ pub mod pallet {
/// This chain's Universal Location.
type UniversalLocation: Get<InteriorMultiLocation>;

//The paraId of this chain.
type SelfParaId: Get<ParaId>;

type TransferCallCreator: primitives::TransferCallCreator<
MultiAddress<Self::AccountId, ()>,
BalanceOf<Self>,
<Self as frame_system::Config>::RuntimeCall,
>;

/// The way to retreave the reserve of a MultiAsset. This can be
/// configured to accept absolute or relative paths for self tokens
type ReserveProvider: Reserve;

/// Self chain location.
#[pallet::constant]
type SelfLocation: Get<MultiLocation>;
}

const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
Expand Down Expand Up @@ -269,6 +277,8 @@ pub mod pallet {
BadVersion,
UnsupportedFeePayment,
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
CannotReanchor,
/// Invalid asset location.
InvalidAssetLocation,
}

#[pallet::event]
Expand Down Expand Up @@ -375,7 +385,8 @@ pub mod pallet {
/// * `DuplicateTask`: There can be no duplicate tasks.
/// * `TimeTooFarOut`: Execution time or frequency are past the max time horizon.
/// * `TimeSlotFull`: Time slot is full. No more tasks can be scheduled for this time.
/// * `UnsupportedFeePayment`: Time slot is full. No more tasks can be scheduled for this time.
/// * `UnsupportedFeePayment`: Unsupported fee payment.
/// * `InvalidAssetLocation` Invalid asset location.
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_xcmp_task_full(schedule.number_of_executions()))]
pub fn schedule_xcmp_task(
Expand All @@ -393,10 +404,18 @@ pub mod pallet {
MultiLocation::try_from(*destination).map_err(|()| Error::<T>::BadVersion)?;
let schedule_fee =
MultiLocation::try_from(*schedule_fee).map_err(|()| Error::<T>::BadVersion)?;

let execution_fee: AssetPayment = *execution_fee;
let execution_fee_location =
MultiLocation::try_from(execution_fee.clone().asset_location)
.map_err(|()| Error::<T>::BadVersion)?;

Self::ensure_supported_execution_fee_location(&execution_fee_location, &destination)?;

let action = Action::XCMP {
destination,
schedule_fee,
execution_fee: *execution_fee,
execution_fee,
encoded_call,
encoded_call_weight,
overall_weight,
Expand Down Expand Up @@ -432,7 +451,6 @@ pub mod pallet {
/// * `DuplicateTask`: There can be no duplicate tasks.
/// * `TimeTooFarOut`: Execution time or frequency are past the max time horizon.
/// * `TimeSlotFull`: Time slot is full. No more tasks can be scheduled for this time.
/// * `UnsupportedFeePayment`: Time slot is full. No more tasks can be scheduled for this time.
/// * `Other("proxy error: expected `ProxyType::Any`")`: schedule_as must be a proxy account of type "any" for the caller.
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_xcmp_task_full(schedule.number_of_executions()).saturating_add(T::DbWeight::get().reads(1)))]
Expand Down Expand Up @@ -1357,21 +1375,12 @@ pub mod pallet {
abort_errors: Vec<Vec<u8>>,
) -> DispatchResult {
match action.clone() {
Action::XCMP { execution_fee, instruction_sequence, .. } => {
Action::XCMP { execution_fee, .. } => {
let asset_location = MultiLocation::try_from(execution_fee.asset_location)
.map_err(|()| Error::<T>::BadVersion)?;
let asset_location = asset_location
.reanchored(
&MultiLocation::new(1, X1(Parachain(T::SelfParaId::get().into()))),
T::UniversalLocation::get(),
)
let _asset_location = asset_location
.reanchored(&T::SelfLocation::get().into(), T::UniversalLocation::get())
.map_err(|_| Error::<T>::CannotReanchor)?;
// Only native token are supported as the XCMP fee for local deductions
if instruction_sequence == InstructionSequence::PayThroughSovereignAccount &&
asset_location != MultiLocation::new(0, Here)
{
Err(Error::<T>::UnsupportedFeePayment)?
}
},
_ => (),
};
Expand Down Expand Up @@ -1530,10 +1539,7 @@ pub mod pallet {

let schedule_fee_location = action.schedule_fee_location::<T>();
let schedule_fee_location = schedule_fee_location
.reanchored(
&MultiLocation::new(1, X1(Parachain(T::SelfParaId::get().into()))),
T::UniversalLocation::get(),
)
.reanchored(&T::SelfLocation::get().into(), T::UniversalLocation::get())
.map_err(|_| Error::<T>::CannotReanchor)?;

let fee = if schedule_fee_location == MultiLocation::default() {
Expand All @@ -1551,6 +1557,22 @@ pub mod pallet {

Ok(fee)
}

pub fn ensure_supported_execution_fee_location(
chrisli30 marked this conversation as resolved.
Show resolved Hide resolved
exeuction_fee_location: &MultiLocation,
destination: &MultiLocation,
) -> Result<(), DispatchError> {
if exeuction_fee_location.chain_part().is_none() {
return Err(Error::<T>::InvalidAssetLocation.into())
}

let self_location = T::SelfLocation::get();
if exeuction_fee_location != &self_location && exeuction_fee_location != destination {
return Err(Error::<T>::UnsupportedFeePayment.into())
}

Ok(())
}
}

impl<T: Config> pallet_valve::Shutdown for Pallet<T> {
Expand Down
Loading