Skip to content

Commit

Permalink
charge fee for scheduling price based task
Browse files Browse the repository at this point in the history
  • Loading branch information
v9n committed Sep 30, 2023
1 parent 2c86504 commit 4d7bb7f
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 94 deletions.
8 changes: 6 additions & 2 deletions pallets/automation-price/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ benchmarks! {
// our task look up will always be O(1) for time
let mut task_ids: Vec<TaskId> = vec![];
for i in 1..100 {
let account_min = T::Currency::minimum_balance().saturating_mul(ED_MULTIPLIER.into());
T::Currency::deposit_creating(&creator, account_min.saturating_mul(DEPOSIT_MULTIPLIER.into()));
direct_task_schedule::<T>(creator.clone(), format!("{:?}", i).as_bytes().to_vec(), i, "gt".as_bytes().to_vec(), i, vec![100, 200, (i % 256) as u8]);
task_ids.push(format!("{:?}", i).as_bytes().to_vec());
}
Expand Down Expand Up @@ -252,11 +254,13 @@ benchmarks! {

emit_event {
let who: T::AccountId = account("call", 1, SEED);
let schedule_as: T::AccountId = account("schedule_as", 1, SEED);
let task_id: TaskId = vec![1,2,3];
} : {
AutomationPrice::<T>::deposit_event(crate::Event::<T>::TaskScheduled {
who: who,
task_id: task_id,
who,
task_id,
schedule_as: Some(schedule_as),
});
}

Expand Down
19 changes: 6 additions & 13 deletions pallets/automation-price/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// limitations under the License.

/// ! Traits and default implementation for paying execution fees.
use crate::{AccountOf, Action, ActionOf, Config, Error, MultiBalanceOf};
use crate::{AccountOf, Action, ActionOf, Config, Error, MultiBalanceOf, Pallet};

use orml_traits::MultiCurrency;
use pallet_xcmp_handler::{InstructionSequence, XcmpTransactor};
Expand All @@ -34,7 +34,6 @@ pub trait HandleFees<T: Config> {
fn pay_checked_fees_for<R, F: FnOnce() -> Result<R, DispatchError>>(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
prereq: F,
) -> Result<R, DispatchError>;
}
Expand All @@ -54,10 +53,9 @@ where
fn pay_checked_fees_for<R, F: FnOnce() -> Result<R, DispatchError>>(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
prereq: F,
) -> Result<R, DispatchError> {
let fee_handler = Self::new(owner, action, executions)?;
let fee_handler = Self::new(owner, action)?;
fee_handler.can_pay_fee().map_err(|_| Error::<T>::InsufficientBalance)?;
let outcome = prereq()?;
fee_handler.pay_fees()?;
Expand Down Expand Up @@ -126,21 +124,16 @@ where
}

/// Builds an instance of the struct
pub fn new(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
) -> Result<Self, DispatchError> {
pub fn new(owner: &AccountOf<T>, action: &ActionOf<T>) -> Result<Self, DispatchError> {
let schedule_fee_location = action.schedule_fee_location::<T>();

// TODO: FIX THIS BEFORE MERGE
let schedule_fee_amount: u128 = 1_000;
//Pallet::<T>::calculate_schedule_fee_amount(action, executions)?.saturated_into();
let schedule_fee_amount: u128 =
Pallet::<T>::calculate_schedule_fee_amount(action)?.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(),
execution_fee.amount.saturating_mul(1_u32.into()).saturated_into(),
_ => 0u32.saturated_into(),
};

Expand Down
117 changes: 95 additions & 22 deletions pallets/automation-price/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use frame_support::{
pallet_prelude::*,
traits::{Currency, ExistenceRequirement},
transactional,
weights::constants::WEIGHT_REF_TIME_PER_SECOND,
};
use frame_system::pallet_prelude::*;
use orml_traits::{FixedConversionRateProvider, MultiCurrency};
Expand Down Expand Up @@ -349,6 +350,9 @@ pub mod pallet {
/// Too Many Assets Created
AssetLimitReached,

FeePaymentError,
CannotReanchor,
UnsupportedFeePayment,
/// The version of the `VersionedMultiLocation` value used is not able
/// to be interpreted.
BadVersion,
Expand All @@ -361,6 +365,7 @@ pub mod pallet {
TaskScheduled {
who: AccountOf<T>,
task_id: TaskId,
schedule_as: Option<AccountOf<T>>,
},
// an event when we're about to run the task
TaskTriggered {
Expand Down Expand Up @@ -581,7 +586,6 @@ pub mod pallet {
Ok(())
}

// TODO: correct weight
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_xcmp_task_extrinsic())]
#[transactional]
Expand All @@ -608,7 +612,6 @@ pub mod pallet {
// TODO: the value to be inserted into the BTree should come from a function that
// extract value from param
//
// TODO: HANDLE FEE to see user can pay fee
let who = ensure_signed(origin)?;
let task_id = Self::generate_task_id();

Expand Down Expand Up @@ -641,12 +644,9 @@ pub mod pallet {
};

Self::validate_and_schedule_task(task)?;
// TODO withdraw fee
//T::FeeHandler::withdraw_fee(&who, fee).map_err(|_| Error::<T>::InsufficientBalance)?;
Ok(())
}

/// TODO: correct weight to use schedule_xcmp_task
/// Schedule a task through XCMP through proxy account to fire an XCMP message with a provided call.
///
/// Before the task can be scheduled the task must past validation checks.
Expand Down Expand Up @@ -1076,34 +1076,107 @@ pub mod pallet {
Err(Error::<T>::InvalidTaskId)?
}

<Tasks<T>>::insert(task.task_id.clone(), &task);
Self::push_task_to_account(&task);
match task.action.clone() {
Action::XCMP { execution_fee, instruction_sequence, .. } => {
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(),
)
.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)?
}
},
_ => (),
};

let key = (&task.chain, &task.exchange, &task.asset_pair, &task.trigger_function);
let fee_result =
T::FeeHandler::pay_checked_fees_for(&task.owner_id, &task.action, || {
<Tasks<T>>::insert(task.task_id.clone(), &task);
Self::push_task_to_account(&task);

if let Some(mut sorted_task_index) = Self::get_sorted_tasks_index(key) {
// TODO: remove hard code and take right param
if let Some(tasks_by_price) = sorted_task_index.get_mut(&(task.trigger_params[0])) {
tasks_by_price.push(task.task_id.clone());
} else {
sorted_task_index.insert(task.trigger_params[0], vec![task.task_id.clone()]);
}
SortedTasksIndex::<T>::insert(key, sorted_task_index);
} else {
let mut sorted_task_index = BTreeMap::<AssetPrice, TaskIdList>::new();
sorted_task_index.insert(task.trigger_params[0], vec![task.task_id.clone()]);
let key =
(&task.chain, &task.exchange, &task.asset_pair, &task.trigger_function);

if let Some(mut sorted_task_index) = Self::get_sorted_tasks_index(key) {
// TODO: remove hard code and take right param
if let Some(tasks_by_price) =
sorted_task_index.get_mut(&(task.trigger_params[0]))
{
tasks_by_price.push(task.task_id.clone());
} else {
sorted_task_index
.insert(task.trigger_params[0], vec![task.task_id.clone()]);
}
SortedTasksIndex::<T>::insert(key, sorted_task_index);
} else {
let mut sorted_task_index = BTreeMap::<AssetPrice, TaskIdList>::new();
sorted_task_index
.insert(task.trigger_params[0], vec![task.task_id.clone()]);

// TODO: sorted based on trigger_function comparison of the parameter
// then at the time of trigger we cut off all the left part of the tree
SortedTasksIndex::<T>::insert(key, sorted_task_index);
// TODO: sorted based on trigger_function comparison of the parameter
// then at the time of trigger we cut off all the left part of the tree
SortedTasksIndex::<T>::insert(key, sorted_task_index);
}

Ok(task.task_id.clone())
});

if let Err(e) = fee_result {
Err(Error::<T>::FeePaymentError)?
}

let schedule_as = match task.action.clone() {
Action::XCMP { schedule_as, .. } => schedule_as,
_ => None,
};

Self::deposit_event(Event::TaskScheduled {
who: task.owner_id,
task_id: task.task_id.clone(),
schedule_as,
});
Ok(())
}

/// Calculates the execution fee for a given action based on weight and num of executions
///
/// Fee saturates at Weight/BalanceOf when there are an unreasonable num of executions
/// In practice, executions is bounded by T::MaxExecutionTimes and unlikely to saturate
pub fn calculate_schedule_fee_amount(
action: &ActionOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
let total_weight = action.execution_weight::<T>()?;

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(),
)
.map_err(|_| Error::<T>::CannotReanchor)?;

let fee = if schedule_fee_location == MultiLocation::default() {
T::ExecutionWeightFee::get()
.saturating_mul(<BalanceOf<T>>::saturated_from(total_weight))
} else {
let raw_fee =
T::FeeConversionRateProvider::get_fee_per_second(&schedule_fee_location)
.ok_or("CouldNotDetermineFeePerSecond")?
.checked_mul(total_weight as u128)
.ok_or("FeeOverflow")
.map(|raw_fee| raw_fee / (WEIGHT_REF_TIME_PER_SECOND as u128))?;
<BalanceOf<T>>::saturated_from(raw_fee)
};

Ok(fee)
}
}

impl<T: Config> pallet_valve::Shutdown for Pallet<T> {
Expand Down
16 changes: 5 additions & 11 deletions pallets/automation-price/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ impl<Test: frame_system::Config> pallet_automation_price::WeightInfo for MockWei
}

fn schedule_xcmp_task_extrinsic() -> Weight {
Weight::from_ref_time(200_000_000_u64)
Weight::from_ref_time(24_000_000_u64)
}

fn cancel_task_extrinsic() -> Weight {
Expand Down Expand Up @@ -443,16 +443,10 @@ pub fn get_xcmp_funds(account: AccountId) {
Balances::set_balance(RawOrigin::Root.into(), account, with_xcm_fees, 0).unwrap();
}

pub fn fund_account(
account: &AccountId,
action_weight: u64,
execution_count: usize,
additional_amount: Option<u128>,
) {
let amount: u128 =
u128::from(action_weight) * ExecutionWeightFee::get() * execution_count as u128 +
additional_amount.unwrap_or(0) +
u128::from(ExistentialDeposit::get());
pub fn fund_account(account: &AccountId, action_weight: u64, additional_amount: Option<u128>) {
let amount: u128 = u128::from(action_weight) * ExecutionWeightFee::get() +
additional_amount.unwrap_or(0) +
u128::from(ExistentialDeposit::get());
_ = <Test as Config>::Currency::deposit_creating(account, amount);
}

Expand Down
Loading

0 comments on commit 4d7bb7f

Please sign in to comment.