Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"benchmarking",
"build-script-utils",
"currencies",
"delay-tasks",
"gradually-update",
"nft",
"oracle",
Expand Down
18 changes: 16 additions & 2 deletions asset-registry/src/mock/para.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ use frame_support::{
};
use frame_system::{EnsureRoot, EnsureSignedBy};
use orml_traits::{
define_combined_task,
location::{AbsoluteReserveProvider, RelativeReserveProvider},
parameter_type_with_key, FixedConversionRateProvider, MultiCurrency,
parameter_type_with_key,
task::{DispatchableTask, TaskResult},
FixedConversionRateProvider, MultiCurrency,
};
use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset};
use orml_xtokens::XtokensTask;
use pallet_xcm::XcmPassthrough;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use polkadot_parachain_primitives::primitives::Sibling;
use scale_info::TypeInfo;
use sp_core::Get;
use sp_runtime::{
traits::{AccountIdConversion, Convert, IdentityLookup},
AccountId32,
AccountId32, DispatchResult, RuntimeDebug,
};
use xcm::v4::{prelude::*, Weight};
use xcm_builder::{
Expand Down Expand Up @@ -310,6 +315,13 @@ parameter_type_with_key! {
};
}

define_combined_task! {
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub enum DelayedTasks {
Xtokens(XtokensTask<Runtime>),
}
}

impl orml_xtokens::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
Expand All @@ -327,6 +339,8 @@ impl orml_xtokens::Config for Runtime {
type ReserveProvider = RelativeReserveProvider;
type RateLimiter = ();
type RateLimiterId = ();
type Task = ();
type DelayTasks = orml_xtokens::DisabledDelayTask<Runtime>;
}

impl orml_xcm::Config for Runtime {
Expand Down
50 changes: 50 additions & 0 deletions delay-tasks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[package]
name = "orml-delay-tasks"
description = "Scheduler delay task and execute."
repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/auction"
license = "Apache-2.0"
version = "0.7.0"
authors = ["Acala Developers"]
edition = "2021"

[dependencies]
log = { workspace = true }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
serde = { workspace = true, optional = true }

frame-support = { workspace = true }
frame-system = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

xcm = { workspace = true }

orml-traits = { path = "../traits", version = "0.7.0", default-features = false }
orml-xtokens = { path = "../xtokens", version = "0.7.0", default-features = false }

[dev-dependencies]
sp-core = { workspace = true, features = ["std"] }
sp-io = { workspace = true, features = ["std"] }
pallet-preimage = { workspace = true, features = ["std"] }
pallet-scheduler = { workspace = true, features = ["std"] }

[features]
default = [ "std" ]
std = [
"frame-support/std",
"frame-system/std",
"orml-traits/std",
"orml-xtokens/std",
"parity-scale-codec/std",
"scale-info/std",
"serde",
"sp-runtime/std",
"sp-std/std",
"xcm/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
289 changes: 289 additions & 0 deletions delay-tasks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
pallet_prelude::*,
traits::{
schedule::{v1::Named as ScheduleNamed, DispatchTime},
OriginTrait,
},
weights::Weight,
};
use frame_system::pallet_prelude::*;
use orml_traits::{
task::{DelayTaskHooks, DelayTasksManager, DispatchableTask, TaskResult},
MultiCurrency, NamedMultiReservableCurrency,
};
use parity_scale_codec::FullCodec;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{CheckedAdd, Convert, Zero},
ArithmeticError,
};
use sp_std::fmt::Debug;
use sp_std::marker::PhantomData;
use xcm::v4::prelude::*;

pub use module::*;

mod mock;
mod tests;

pub const DELAY_TASK_ID: [u8; 8] = *b"orml/dts";

/// A delayed origin. Can only be dispatched via `dispatch_as` with a delay.
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct DelayedExecuteOrigin;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should try to reuse the one from orml-authority. we can move it to orml-utils


pub struct EnsureDelayed;
impl<O: Into<Result<DelayedExecuteOrigin, O>> + From<DelayedExecuteOrigin>> EnsureOrigin<O> for EnsureDelayed {
type Success = ();
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|_| Ok(()))
}

#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(DelayedExecuteOrigin))
}
}

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

type Nonce = u64;

/// Origin for the delay tasks module.
#[pallet::origin]
pub type Origin = DelayedExecuteOrigin;

#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

type RuntimeCall: Parameter + From<Call<Self>>;

/// The outer origin type.
type RuntimeOrigin: From<DelayedExecuteOrigin>
+ From<<Self as frame_system::Config>::RuntimeOrigin>
+ OriginTrait<PalletsOrigin = Self::PalletsOrigin>;

/// The caller origin, overarching type of all pallets origins.
type PalletsOrigin: Parameter + Into<<Self as frame_system::Config>::RuntimeOrigin>;

type DelayOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;

type GovernanceOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;

type Task: DispatchableTask + FullCodec + Debug + Clone + PartialEq + TypeInfo;

/// The Scheduler.
type Scheduler: ScheduleNamed<BlockNumberFor<Self>, <Self as Config>::RuntimeCall, Self::PalletsOrigin>;

type DelayTaskHooks: DelayTaskHooks<Self::Task>;

/// Convert `Location` to `CurrencyId`.
type CurrencyIdConvert: Convert<
Location,
Option<<Self::Currency as MultiCurrency<Self::AccountId>>::CurrencyId>,
>;

type Currency: NamedMultiReservableCurrency<Self::AccountId>;

type ReserveId: Get<<Self::Currency as NamedMultiReservableCurrency<Self::AccountId>>::ReserveIdentifier>;
}

#[pallet::error]
pub enum Error<T> {
InvalidDelayBlock,
InvalidId,
FailedToSchedule,
}

#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
DelayedTaskAdded {
id: Nonce,
task: T::Task,
execute_block: BlockNumberFor<T>,
},
DelayedTaskExecuted {
id: Nonce,
result: DispatchResult,
},
DelayedTaskReDelayed {
id: Nonce,
execute_block: BlockNumberFor<T>,
},
DelayedTaskCanceled {
id: Nonce,
},
}

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

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

#[pallet::storage]
#[pallet::getter(fn next_delayed_task_id)]
pub type NextDelayedTaskId<T: Config> = StorageValue<_, Nonce, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn delayed_tasks)]
pub type DelayedTasks<T: Config> = StorageMap<_, Twox64Concat, Nonce, (T::Task, BlockNumberFor<T>), OptionQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(Weight::zero())]
pub fn delayed_execute(origin: OriginFor<T>, id: Nonce) -> DispatchResult {
T::DelayOrigin::ensure_origin(origin)?;

let execute_result = Self::do_delayed_execute(id)?;

Self::deposit_event(Event::<T>::DelayedTaskExecuted {
id,
result: execute_result.result,
});
Ok(())
}

#[pallet::call_index(1)]
#[pallet::weight(Weight::zero())]
pub fn reschedule_delay_task(
origin: OriginFor<T>,
id: Nonce,
when: DispatchTime<BlockNumberFor<T>>,
) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;

DelayedTasks::<T>::try_mutate_exists(id, |maybe_task| -> DispatchResult {
let (_, execute_block) = maybe_task.as_mut().ok_or(Error::<T>::InvalidId)?;

let now = frame_system::Pallet::<T>::block_number();
let new_execute_block = match when {
DispatchTime::At(x) => x,
DispatchTime::After(x) => x.checked_add(&now).ok_or(ArithmeticError::Overflow)?,
};
ensure!(new_execute_block > now, Error::<T>::InvalidDelayBlock);

*execute_block = new_execute_block;

T::Scheduler::reschedule_named((&DELAY_TASK_ID, id).encode(), DispatchTime::At(new_execute_block))
.map_err(|_| Error::<T>::FailedToSchedule)?;

Self::deposit_event(Event::<T>::DelayedTaskReDelayed {
id,
execute_block: new_execute_block,
});
Ok(())
})?;

Ok(())
}

#[pallet::call_index(2)]
#[pallet::weight(Weight::zero())]
pub fn cancel_delayed_task(origin: OriginFor<T>, id: Nonce) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;

let (task, _) = DelayedTasks::<T>::take(id).ok_or(Error::<T>::InvalidId)?;

// pre cancel
T::DelayTaskHooks::on_cancel(&task)?;

T::Scheduler::cancel_named((&DELAY_TASK_ID, id).encode()).map_err(|_| Error::<T>::FailedToSchedule)?;

Self::deposit_event(Event::<T>::DelayedTaskCanceled { id });
Ok(())
}
}

impl<T: Config> Pallet<T> {
pub(crate) fn do_delayed_execute(id: Nonce) -> sp_std::result::Result<TaskResult, DispatchError> {
let (delayed_task, _) = DelayedTasks::<T>::take(id).ok_or(Error::<T>::InvalidId)?;

// pre delayed dispatch
T::DelayTaskHooks::pre_delayed_execute(&delayed_task)?;

Ok(delayed_task.dispatch(Weight::zero()))
}

/// Retrieves the next delayed task ID from storage, and increment it by
/// one.
fn get_next_delayed_task_id() -> Result<Nonce, DispatchError> {
NextDelayedTaskId::<T>::mutate(|current| -> Result<Nonce, DispatchError> {
let id = *current;

*current = current.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Ok(id)
})
}
}

impl<T: Config> DelayTasksManager<T::Task, BlockNumberFor<T>> for Pallet<T> {
fn add_delay_task(task: T::Task, delay_blocks: BlockNumberFor<T>) -> DispatchResult {
ensure!(!delay_blocks.is_zero(), Error::<T>::InvalidDelayBlock);
let execute_block = frame_system::Pallet::<T>::block_number()
.checked_add(&delay_blocks)
.ok_or(ArithmeticError::Overflow)?;

// pre schedule delay task
T::DelayTaskHooks::pre_delay(&task)?;

let id = Self::get_next_delayed_task_id()?;
let delayed_origin: <T as Config>::RuntimeOrigin = From::from(DelayedExecuteOrigin);
let pallets_origin = delayed_origin.caller().clone();

T::Scheduler::schedule_named(
(&DELAY_TASK_ID, id).encode(),
DispatchTime::At(execute_block),
None,
Zero::zero(),
pallets_origin,
<T as Config>::RuntimeCall::from(Call::<T>::delayed_execute { id }),
)
.map_err(|_| Error::<T>::FailedToSchedule)?;

DelayedTasks::<T>::insert(id, (&task, execute_block));

Self::deposit_event(Event::<T>::DelayedTaskAdded {
id,
task,
execute_block,
});
Ok(())
}
}

pub struct DelayedXtokensTaskHooks<T>(PhantomData<T>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should avoid xcm/xtokens code in this pallet

impl<T: Config + orml_xtokens::Config> DelayTaskHooks<orml_xtokens::XtokensTask<T>> for DelayedXtokensTaskHooks<T> {
fn pre_delay(task: &orml_xtokens::XtokensTask<T>) -> DispatchResult {
match task {
orml_xtokens::XtokensTask::<T>::TransferAssets { who, assets, fee, .. } => {}
}

Ok(())
}

fn pre_delayed_execute(task: &orml_xtokens::XtokensTask<T>) -> DispatchResult {
match task {
orml_xtokens::XtokensTask::<T>::TransferAssets { who, assets, fee, .. } => {}
}

Ok(())
}

fn on_cancel(task: &orml_xtokens::XtokensTask<T>) -> DispatchResult {
match task {
orml_xtokens::XtokensTask::<T>::TransferAssets { who, assets, fee, .. } => {}
}

Ok(())
}
}
}
Loading