Skip to content

Commit

Permalink
Merge branch 'master' into specify-currency-for-execution-fee
Browse files Browse the repository at this point in the history
  • Loading branch information
imstar15 authored Nov 6, 2023
2 parents b30e2b5 + 8d10b8a commit 9c7e873
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 4 deletions.
37 changes: 34 additions & 3 deletions pallets/automation-time/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ fn schedule_notify_tasks<T: Config>(owner: T::AccountId, times: Vec<u64>, count:
task_id
}

fn schedule_xcmp_tasks<T: Config>(owner: T::AccountId, times: Vec<u64>, count: u32) -> Vec<u8> {
fn schedule_xcmp_tasks<T: Config>(
owner: T::AccountId,
schedule_as: Option<T::AccountId>,
times: Vec<u64>,
count: u32,
) -> Vec<u8> {
let transfer_amount = T::Currency::minimum_balance().saturating_mul(ED_MULTIPLIER.into());
T::Currency::deposit_creating(
&owner,
Expand All @@ -92,6 +97,7 @@ fn schedule_xcmp_tasks<T: Config>(owner: T::AccountId, times: Vec<u64>, count: u
vec![4, 5, 6],
Weight::from_parts(5_000, 0),
Weight::from_parts(10_000, 0),
schedule_as.clone(),
InstructionSequence::PayThroughSovereignAccount,
vec![],
)
Expand Down Expand Up @@ -176,7 +182,7 @@ benchmarks! {

let fee = AssetPayment { asset_location: MultiLocation::new(0, Here).into(), amount: 100u128 };

let task_id = schedule_xcmp_tasks::<T>(caller.clone(), times, max_tasks_per_slot - 1);
let task_id = schedule_xcmp_tasks::<T>(caller.clone(), None, times, max_tasks_per_slot - 1);
let foreign_currency_amount = T::MultiCurrency::minimum_balance(currency_id.into())
.saturating_add(1u32.into())
.saturating_mul(ED_MULTIPLIER.into())
Expand Down Expand Up @@ -282,6 +288,31 @@ benchmarks! {
let task_id = schedule_notify_tasks::<T>(caller.clone(), times, T::MaxTasksPerSlot::get());
}: force_cancel_task(RawOrigin::Root, caller, task_id)

cancel_task_with_schedule_as_full {
let caller: T::AccountId = account("caller", 0, SEED);
let schedule_as: T::AccountId = account("schedule_as", 0, SEED);
let time: u64 = 10800;
let para_id: u32 = 2001;
let mut times: Vec<u64> = vec![];

// Fill up all time slots
for i in 0..T::MaxExecutionTimes::get() {
let hour: u64 = (3600 * (i + 1)).try_into().unwrap();
times.push(hour);
}

let local_para_id: u32 = 2114;
let destination = MultiLocation::new(1, X1(Parachain(para_id)));
let local_sovereign_account: T::AccountId = Sibling::from(local_para_id).into_account_truncating();
T::Currency::deposit_creating(
&local_sovereign_account,
T::Currency::minimum_balance().saturating_mul(DEPOSIT_MULTIPLIER.into()),
);

let fee = AssetPayment { asset_location: MultiLocation::new(1, X1(Parachain(para_id))).into(), amount: 1000u128 };
let task_id = schedule_xcmp_tasks::<T>(caller.clone(), Some(schedule_as.clone()), times, 1);
}: cancel_task_with_schedule_as(RawOrigin::Signed(schedule_as), caller, task_id)

run_xcmp_task {
let caller: T::AccountId = account("caller", 0, SEED);
let time: u64 = 10800;
Expand All @@ -298,7 +329,7 @@ benchmarks! {

let fee = AssetPayment { asset_location: MultiLocation::new(1, X1(Parachain(para_id))).into(), amount: 1000u128 };

let task_id = schedule_xcmp_tasks::<T>(caller.clone(), vec![time], 1);
let task_id = schedule_xcmp_tasks::<T>(caller.clone(), None, vec![time], 1);
}: { AutomationTime::<T>::run_xcmp_task(destination, caller, fee, call, Weight::from_parts(100_000, 0), Weight::from_parts(200_000, 0), InstructionSequence::PayThroughSovereignAccount) }

run_auto_compound_delegated_stake_task {
Expand Down
35 changes: 35 additions & 0 deletions pallets/automation-time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ pub mod pallet {
TimeSlotFull,
/// The task does not exist.
TaskDoesNotExist,
/// The task schedule_as does not match.
TaskScheduleAsNotMatch,
/// Block time not set.
BlockTimeNotSet,
/// Insufficient balance to pay execution fee.
Expand Down Expand Up @@ -586,6 +588,36 @@ pub mod pallet {

Ok(())
}

/// Cancel task by schedule_as
///
/// # Parameters
/// * `schedule_as`: The schedule_as account of the task.
/// * `task_id`: The id of the task.
///
/// # Errors
/// * `TaskDoesNotExist`: The task does not exist.
/// * `TaskScheduleAsNotMatch`: The schedule_as account of the task does not match.
#[pallet::call_index(8)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_task_with_schedule_as_full())]
pub fn cancel_task_with_schedule_as(
origin: OriginFor<T>,
owner_id: AccountOf<T>,
task_id: TaskIdV2,
) -> DispatchResult {
let who = ensure_signed(origin)?;

let task = AccountTasks::<T>::get(owner_id, task_id.clone())
.ok_or(Error::<T>::TaskDoesNotExist)?;

if !matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &who)
{
return Err(Error::<T>::TaskScheduleAsNotMatch.into())
}

Self::remove_task(task_id, task);
Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -1260,7 +1292,10 @@ pub mod pallet {
});
}

// TODO: Add refund reserved execution fees here

AccountTasks::<T>::remove(task.owner_id.clone(), task_id.clone());

Self::deposit_event(Event::TaskCancelled { who: task.owner_id, task_id });
}

Expand Down
3 changes: 3 additions & 0 deletions pallets/automation-time/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ impl<Test: frame_system::Config> pallet_automation_time::WeightInfo for MockWeig
fn force_cancel_scheduled_task_full() -> Weight {
Weight::zero()
}
fn cancel_task_with_schedule_as_full() -> Weight {
Weight::zero()
}
fn run_xcmp_task() -> Weight {
Weight::from_parts(20_000, 0)
}
Expand Down
193 changes: 193 additions & 0 deletions pallets/automation-time/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,199 @@ fn will_not_emit_task_completed_event_when_task_canceled() {
})
}

// The task will be remove from account tasks when the task is canceled with schedule_as.
#[test]
fn will_remove_task_from_account_tasks_when_task_canceled_with_schedule_as() {
new_test_ext(START_BLOCK_TIME).execute_with(|| {
let schedule_as = AccountId32::new(DELEGATOR_ACCOUNT);
let task_owner = AccountId32::new(PROXY_ACCOUNT);
let destination = MultiLocation::new(1, X1(Parachain(PARA_ID)));
let task_id = FIRST_TASK_ID.to_vec();

fund_account(&task_owner, 900_000_000, 2, Some(0));

let call: <Test as frame_system::Config>::RuntimeCall =
frame_system::Call::remark_with_event { remark: vec![0] }.into();

// Schedule task
assert_ok!(AutomationTime::schedule_xcmp_task_through_proxy(
RuntimeOrigin::signed(task_owner.clone()),
ScheduleParam::Fixed { execution_times: vec![SCHEDULED_TIME] },
Box::new(destination.into()),
Box::new(NATIVE_LOCATION.into()),
Box::new(AssetPayment {
asset_location: MultiLocation::new(0, Here).into(),
amount: 10
}),
call.encode(),
Weight::from_parts(100_000, 0),
Weight::from_parts(200_000, 0),
schedule_as.clone(),
));

// Check if the task's schedule_as is correct
let task = AccountTasks::<Test>::get(task_owner.clone(), task_id.clone());
assert_eq!(task.is_some(), true);

let task = task.unwrap();
assert_eq!(
matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &schedule_as),
true
);

// Cancel task with schedule_as
assert_ok!(AutomationTime::cancel_task_with_schedule_as(
RuntimeOrigin::signed(schedule_as),
task_owner.clone(),
task_id.clone(),
));

// Verify that the task is no longer in the accountTasks.
assert_eq!(AutomationTime::get_account_task(task_owner, task_id), None);
})
}

// Calling cancel_task_with_schedule_as with a schedule_as account will cause TaskScheduleAsNotMatch error.
#[test]
fn cancel_task_with_incorrect_schedule_as_will_fail() {
new_test_ext(START_BLOCK_TIME).execute_with(|| {
let schedule_as = AccountId32::new(DELEGATOR_ACCOUNT);
let task_owner = AccountId32::new(PROXY_ACCOUNT);
let destination = MultiLocation::new(1, X1(Parachain(PARA_ID)));
let task_id = FIRST_TASK_ID.to_vec();

fund_account(&task_owner, 900_000_000, 2, Some(0));

let call: <Test as frame_system::Config>::RuntimeCall =
frame_system::Call::remark_with_event { remark: vec![0] }.into();

// Schedule task
assert_ok!(AutomationTime::schedule_xcmp_task_through_proxy(
RuntimeOrigin::signed(task_owner.clone()),
ScheduleParam::Fixed { execution_times: vec![SCHEDULED_TIME] },
Box::new(destination.into()),
Box::new(NATIVE_LOCATION.into()),
Box::new(AssetPayment {
asset_location: MultiLocation::new(0, Here).into(),
amount: 10
}),
call.encode(),
Weight::from_parts(100_000, 0),
Weight::from_parts(200_000, 0),
schedule_as.clone(),
));

// Check if the task's schedule_as is correct
let task = AccountTasks::<Test>::get(task_owner.clone(), task_id.clone());
assert_eq!(task.is_some(), true);

let task = task.unwrap();
assert_eq!(
matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &schedule_as),
true
);

// Cancel task with incorrect schedule_as
// It will throw TaskScheduleAsNotMatch error
assert_noop!(
AutomationTime::cancel_task_with_schedule_as(
RuntimeOrigin::signed(AccountId32::new(ALICE)),
task_owner.clone(),
task_id.clone(),
),
Error::<Test>::TaskScheduleAsNotMatch
);

// Assert that the task is still present in accountTasks.
assert_eq!(
matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &schedule_as),
true
);
})
}

// Calling cancel_task_with_schedule_as with a non-existent taskid will cause TaskDoesNotExist error.
#[test]
fn cancel_with_schedule_as_and_non_existent_taskid_will_fail() {
new_test_ext(START_BLOCK_TIME).execute_with(|| {
let schedule_as = AccountId32::new(DELEGATOR_ACCOUNT);
let task_owner = AccountId32::new(PROXY_ACCOUNT);
let task_id = FIRST_TASK_ID.to_vec();

fund_account(&task_owner, 900_000_000, 2, Some(0));

// Cancel task with non-existent taskid
// It will throw TaskDoesNotExist error
assert_noop!(
AutomationTime::cancel_task_with_schedule_as(
RuntimeOrigin::signed(schedule_as),
task_owner.clone(),
task_id.clone(),
),
Error::<Test>::TaskDoesNotExist
);
})
}

// Calling cancel_task_with_schedule_as with an incorrect owner will cause TaskDoesNotExist error.
#[test]
fn cancel_with_schedule_as_and_incorrect_owner_will_fail() {
new_test_ext(START_BLOCK_TIME).execute_with(|| {
let schedule_as = AccountId32::new(DELEGATOR_ACCOUNT);
let task_owner = AccountId32::new(PROXY_ACCOUNT);
let destination = MultiLocation::new(1, X1(Parachain(PARA_ID)));
let task_id = FIRST_TASK_ID.to_vec();

fund_account(&task_owner, 900_000_000, 2, Some(0));

let call: <Test as frame_system::Config>::RuntimeCall =
frame_system::Call::remark_with_event { remark: vec![0] }.into();

// Schedule task
assert_ok!(AutomationTime::schedule_xcmp_task_through_proxy(
RuntimeOrigin::signed(task_owner.clone()),
ScheduleParam::Fixed { execution_times: vec![SCHEDULED_TIME] },
Box::new(destination.into()),
Box::new(NATIVE_LOCATION.into()),
Box::new(AssetPayment {
asset_location: MultiLocation::new(0, Here).into(),
amount: 10
}),
call.encode(),
Weight::from_parts(100_000, 0),
Weight::from_parts(200_000, 0),
schedule_as.clone(),
));

// Check if the task's schedule_as is correct
let task = AccountTasks::<Test>::get(task_owner.clone(), task_id.clone());
assert_eq!(task.is_some(), true);

let task = task.unwrap();
assert_eq!(
matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &schedule_as),
true
);

// Cancel task with incorrect owner
// It will throw TaskDoesNotExist error
assert_noop!(
AutomationTime::cancel_task_with_schedule_as(
RuntimeOrigin::signed(schedule_as.clone()),
AccountId32::new(ALICE),
task_id.clone(),
),
Error::<Test>::TaskDoesNotExist
);

// Assert that the task is still present in accountTasks.
assert_eq!(
matches!(task.clone().action, Action::XCMP { schedule_as: Some(ref s), .. } if s == &schedule_as),
true
);
})
}

// When a task fails, the TaskCompleted event will still be emitted.
#[test]
fn will_emit_task_completed_event_when_task_failed() {
Expand Down
3 changes: 2 additions & 1 deletion pallets/automation-time/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ impl<AccountId: Clone, Balance> Task<AccountId, Balance> {
encoded_call: Vec<u8>,
encoded_call_weight: Weight,
overall_weight: Weight,
schedule_as: Option<AccountId>,
instruction_sequence: InstructionSequence,
abort_errors: Vec<Vec<u8>>,
) -> Result<Self, DispatchError> {
Expand All @@ -258,7 +259,7 @@ impl<AccountId: Clone, Balance> Task<AccountId, Balance> {
encoded_call,
encoded_call_weight,
overall_weight,
schedule_as: None,
schedule_as,
instruction_sequence,
};
let schedule = Schedule::new_fixed_schedule::<T>(execution_times)?;
Expand Down
11 changes: 11 additions & 0 deletions pallets/automation-time/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub trait WeightInfo {
fn cancel_scheduled_task_full() -> Weight;
fn force_cancel_scheduled_task() -> Weight;
fn force_cancel_scheduled_task_full() -> Weight;
fn cancel_task_with_schedule_as_full() -> Weight;
fn run_xcmp_task() -> Weight;
fn run_auto_compound_delegated_stake_task() -> Weight;
fn run_dynamic_dispatch_action() -> Weight;
Expand Down Expand Up @@ -227,6 +228,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().reads(39 as u64))
.saturating_add(T::DbWeight::get().writes(37 as u64))
}

fn cancel_task_with_schedule_as_full() -> Weight {
Weight::zero()
}

// Storage: ParachainInfo ParachainId (r:1 w:0)
// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
// Storage: UnknownTokens ConcreteFungibleBalances (r:1 w:0)
Expand Down Expand Up @@ -484,6 +490,11 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(39 as u64))
.saturating_add(RocksDbWeight::get().writes(37 as u64))
}

fn cancel_task_with_schedule_as_full() -> Weight {
Weight::zero()
}

// Storage: ParachainInfo ParachainId (r:1 w:0)
// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
// Storage: UnknownTokens ConcreteFungibleBalances (r:1 w:0)
Expand Down

0 comments on commit 9c7e873

Please sign in to comment.