Skip to content
Merged
41 changes: 40 additions & 1 deletion authority/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use frame_support::{
};
use frame_system::pallet_prelude::*;
use sp_runtime::{
traits::{CheckedSub, Dispatchable, Saturating},
traits::{CheckedSub, Dispatchable, Hash, Saturating},
ArithmeticError, DispatchError, DispatchResult, RuntimeDebug,
};
use sp_std::prelude::*;
Expand Down Expand Up @@ -172,6 +172,10 @@ pub mod module {
FailedToFastTrack,
/// Failed to delay a task.
FailedToDelay,
/// Call is not authorized.
CallNotAuthorized,
/// Triggering the call is not permitted.
TriggerCallNotPermitted,
}

#[pallet::event]
Expand All @@ -187,12 +191,21 @@ pub mod module {
Delayed(T::PalletsOrigin, ScheduleTaskIndex, T::BlockNumber),
/// A scheduled call is cancelled. [origin, index]
Cancelled(T::PalletsOrigin, ScheduleTaskIndex),
/// A call is authorized. \[hash, caller\]
AuthorizedCall(T::Hash, Option<T::AccountId>),
/// Saved call is triggered. \[hash, caller\]
TriggeredCallBy(T::Hash, T::AccountId),
}

#[pallet::storage]
#[pallet::getter(fn next_task_index)]
pub type NextTaskIndex<T: Config> = StorageValue<_, ScheduleTaskIndex, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn saved_calls)]
pub type SavedCalls<T: Config> =
StorageMap<_, Twox64Concat, T::Hash, (CallOf<T>, Option<T::AccountId>), OptionQuery>;
Comment thread
xlc marked this conversation as resolved.
Outdated

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

Expand Down Expand Up @@ -325,5 +338,31 @@ pub mod module {
Self::deposit_event(Event::Cancelled(*initial_origin, task_id));
Ok(())
}

#[pallet::weight(0)]
Comment thread
ermalkaleci marked this conversation as resolved.
Outdated
pub fn authorize_call(
origin: OriginFor<T>,
call: Box<CallOf<T>>,
caller: Option<T::AccountId>,
) -> DispatchResult {
ensure_root(origin)?;
let hash = T::Hashing::hash_of(&call);
SavedCalls::<T>::insert(hash, (call, caller.clone()));
Comment thread
ermalkaleci marked this conversation as resolved.
Self::deposit_event(Event::AuthorizedCall(hash, caller));
Ok(())
}

#[pallet::weight(0)]
pub fn trigger_call(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
let who = ensure_signed(origin)?;
let (call, maybe_account) = Self::saved_calls(&hash).ok_or(Error::<T>::CallNotAuthorized)?;
if let Some(account) = maybe_account {
ensure!(who == account, Error::<T>::TriggerCallNotPermitted);
}
let result = call.dispatch(OriginFor::<T>::root());
Self::deposit_event(Event::TriggeredCallBy(hash, who));
Self::deposit_event(Event::Dispatched(result.map(|_| ()).map_err(|e| e.error)));
Ok(())
}
}
}
69 changes: 69 additions & 0 deletions authority/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,72 @@ fn call_size_limit() {
If the limit is too strong, maybe consider increasing the limit",
);
}

#[test]
fn authorize_call_works() {
ExtBuilder::default().build().execute_with(|| {
run_to_block(1);
let ensure_root_call = Call::System(frame_system::Call::fill_block(Perbill::one()));
let call = Call::Authority(authority::Call::dispatch_as(
MockAsOriginId::Root,
Box::new(ensure_root_call),
));
let hash = <Runtime as frame_system::Config>::Hashing::hash_of(&call);

// works without account
assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), None)));
System::assert_last_event(mock::Event::Authority(Event::AuthorizedCall(hash, None)));

// works with account
assert_ok!(Authority::authorize_call(
Origin::root(),
Box::new(call.clone()),
Some(1)
));
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));
System::assert_last_event(mock::Event::Authority(Event::AuthorizedCall(hash, Some(1))));
});
}

#[test]
fn trigger_call_works() {
ExtBuilder::default().build().execute_with(|| {
run_to_block(1);
let ensure_root_call = Call::System(frame_system::Call::fill_block(Perbill::one()));
let call = Call::Authority(authority::Call::dispatch_as(
MockAsOriginId::Root,
Box::new(ensure_root_call),
));
let hash = <Runtime as frame_system::Config>::Hashing::hash_of(&call);

// call not authorized yet
assert_noop!(
Authority::trigger_call(Origin::signed(1), hash),
Error::<Runtime>::CallNotAuthorized
);

// works without caller
assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
assert_ok!(Authority::trigger_call(Origin::signed(1), hash));
System::assert_has_event(mock::Event::Authority(Event::TriggeredCallBy(hash, 1)));
System::assert_last_event(mock::Event::Authority(Event::Dispatched(Ok(()))));

// works with caller 1
assert_ok!(Authority::authorize_call(
Origin::root(),
Box::new(call.clone()),
Some(1)
));
// caller 2 is not permitted to trigger the call
assert_noop!(
Authority::trigger_call(Origin::signed(2), hash),
Error::<Runtime>::TriggerCallNotPermitted
);

// caller 1 triggering the call
assert_ok!(Authority::trigger_call(Origin::signed(1), hash));
System::assert_has_event(mock::Event::Authority(Event::TriggeredCallBy(hash, 1)));
System::assert_last_event(mock::Event::Authority(Event::Dispatched(Ok(()))));
});
}