Skip to content

Commit d0f9033

Browse files
authored
authorize root call (#603)
* authorize root call * fmt, clippy & update tests * remove authorized call method * update * add weight * add weights for new calls * update trigger call to include call weight * clippy * Revert "clippy" This reverts commit e003de9. * Revert "update trigger call to include call weight" This reverts commit 8c3375c. * add call_weight_bound * fix weight bound
1 parent ff5edef commit d0f9033

File tree

3 files changed

+233
-14
lines changed

3 files changed

+233
-14
lines changed

authority/src/lib.rs

+77-3
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ use frame_support::{
2828
},
2929
weights::GetDispatchInfo,
3030
};
31-
use frame_system::pallet_prelude::*;
31+
use frame_system::{pallet_prelude::*, EnsureOneOf, EnsureRoot, EnsureSigned};
3232
use sp_runtime::{
33-
traits::{CheckedSub, Dispatchable, Saturating},
34-
ArithmeticError, DispatchError, DispatchResult, RuntimeDebug,
33+
traits::{CheckedSub, Dispatchable, Hash, Saturating},
34+
ArithmeticError, DispatchError, DispatchResult, Either, RuntimeDebug,
3535
};
3636
use sp_std::prelude::*;
3737

@@ -172,6 +172,12 @@ pub mod module {
172172
FailedToFastTrack,
173173
/// Failed to delay a task.
174174
FailedToDelay,
175+
/// Call is not authorized.
176+
CallNotAuthorized,
177+
/// Triggering the call is not permitted.
178+
TriggerCallNotPermitted,
179+
/// Call weight bound is wrong.
180+
WrongCallWeightBound,
175181
}
176182

177183
#[pallet::event]
@@ -187,12 +193,22 @@ pub mod module {
187193
Delayed(T::PalletsOrigin, ScheduleTaskIndex, T::BlockNumber),
188194
/// A scheduled call is cancelled. [origin, index]
189195
Cancelled(T::PalletsOrigin, ScheduleTaskIndex),
196+
/// A call is authorized. \[hash, caller\]
197+
AuthorizedCall(T::Hash, Option<T::AccountId>),
198+
/// An authorized call was removed. \[hash\]
199+
RemovedAuthorizedCall(T::Hash),
200+
/// An authorized call was triggered. \[hash, caller\]
201+
TriggeredCallBy(T::Hash, T::AccountId),
190202
}
191203

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

208+
#[pallet::storage]
209+
#[pallet::getter(fn saved_calls)]
210+
pub type SavedCalls<T: Config> = StorageMap<_, Identity, T::Hash, (CallOf<T>, Option<T::AccountId>), OptionQuery>;
211+
196212
#[pallet::pallet]
197213
pub struct Pallet<T>(_);
198214

@@ -325,5 +341,63 @@ pub mod module {
325341
Self::deposit_event(Event::Cancelled(*initial_origin, task_id));
326342
Ok(())
327343
}
344+
345+
#[pallet::weight(T::WeightInfo::authorize_call())]
346+
pub fn authorize_call(
347+
origin: OriginFor<T>,
348+
call: Box<CallOf<T>>,
349+
caller: Option<T::AccountId>,
350+
) -> DispatchResult {
351+
ensure_root(origin)?;
352+
let hash = T::Hashing::hash_of(&call);
353+
SavedCalls::<T>::insert(hash, (call, caller.clone()));
354+
Self::deposit_event(Event::AuthorizedCall(hash, caller));
355+
Ok(())
356+
}
357+
358+
#[pallet::weight(T::WeightInfo::remove_authorized_call())]
359+
pub fn remove_authorized_call(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
360+
let root_or_sigend =
361+
EnsureOneOf::<T::AccountId, EnsureRoot<T::AccountId>, EnsureSigned<T::AccountId>>::ensure_origin(
362+
origin,
363+
)?;
364+
365+
SavedCalls::<T>::try_mutate_exists(hash, |maybe_call| {
366+
let (_, maybe_caller) = maybe_call.take().ok_or(Error::<T>::CallNotAuthorized)?;
367+
match root_or_sigend {
368+
Either::Left(_) => {} // root, do nothing
369+
Either::Right(who) => {
370+
// signed, ensure it's the caller
371+
let caller = maybe_caller.ok_or(Error::<T>::CallNotAuthorized)?;
372+
ensure!(who == caller, Error::<T>::CallNotAuthorized);
373+
}
374+
}
375+
Self::deposit_event(Event::RemovedAuthorizedCall(hash));
376+
Ok(())
377+
})
378+
}
379+
380+
#[pallet::weight(T::WeightInfo::trigger_call().saturating_add(*call_weight_bound))]
381+
pub fn trigger_call(
382+
origin: OriginFor<T>,
383+
hash: T::Hash,
384+
#[pallet::compact] call_weight_bound: Weight,
385+
) -> DispatchResult {
386+
let who = ensure_signed(origin)?;
387+
SavedCalls::<T>::try_mutate_exists(hash, |maybe_call| {
388+
let (call, maybe_caller) = maybe_call.take().ok_or(Error::<T>::CallNotAuthorized)?;
389+
if let Some(caller) = maybe_caller {
390+
ensure!(who == caller, Error::<T>::TriggerCallNotPermitted);
391+
}
392+
ensure!(
393+
call_weight_bound >= call.get_dispatch_info().weight,
394+
Error::<T>::WrongCallWeightBound
395+
);
396+
let result = call.dispatch(OriginFor::<T>::root());
397+
Self::deposit_event(Event::TriggeredCallBy(hash, who));
398+
Self::deposit_event(Event::Dispatched(result.map(|_| ()).map_err(|e| e.error)));
399+
Ok(())
400+
})
401+
}
328402
}
329403
}

authority/src/tests.rs

+129
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,132 @@ fn call_size_limit() {
404404
If the limit is too strong, maybe consider increasing the limit",
405405
);
406406
}
407+
408+
#[test]
409+
fn authorize_call_works() {
410+
ExtBuilder::default().build().execute_with(|| {
411+
run_to_block(1);
412+
let ensure_root_call = Call::System(frame_system::Call::fill_block(Perbill::one()));
413+
let call = Call::Authority(authority::Call::dispatch_as(
414+
MockAsOriginId::Root,
415+
Box::new(ensure_root_call),
416+
));
417+
let hash = <Runtime as frame_system::Config>::Hashing::hash_of(&call);
418+
419+
// works without account
420+
assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
421+
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), None)));
422+
System::assert_last_event(mock::Event::Authority(Event::AuthorizedCall(hash, None)));
423+
424+
// works with account
425+
assert_ok!(Authority::authorize_call(
426+
Origin::root(),
427+
Box::new(call.clone()),
428+
Some(1)
429+
));
430+
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));
431+
System::assert_last_event(mock::Event::Authority(Event::AuthorizedCall(hash, Some(1))));
432+
});
433+
}
434+
435+
#[test]
436+
fn trigger_call_works() {
437+
ExtBuilder::default().build().execute_with(|| {
438+
run_to_block(1);
439+
let ensure_root_call = Call::System(frame_system::Call::fill_block(Perbill::one()));
440+
let call = Call::Authority(authority::Call::dispatch_as(
441+
MockAsOriginId::Root,
442+
Box::new(ensure_root_call),
443+
));
444+
let hash = <Runtime as frame_system::Config>::Hashing::hash_of(&call);
445+
446+
let call_weight_bound = call.get_dispatch_info().weight;
447+
448+
// call not authorized yet
449+
assert_noop!(
450+
Authority::trigger_call(Origin::signed(1), hash, call_weight_bound),
451+
Error::<Runtime>::CallNotAuthorized
452+
);
453+
454+
assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
455+
456+
// wrong call weight bound
457+
assert_noop!(
458+
Authority::trigger_call(Origin::signed(1), hash, call_weight_bound - 1),
459+
Error::<Runtime>::WrongCallWeightBound
460+
);
461+
462+
// works without caller
463+
assert_ok!(Authority::trigger_call(Origin::signed(1), hash, call_weight_bound));
464+
assert_eq!(Authority::saved_calls(&hash), None);
465+
System::assert_has_event(mock::Event::Authority(Event::TriggeredCallBy(hash, 1)));
466+
System::assert_last_event(mock::Event::Authority(Event::Dispatched(Ok(()))));
467+
468+
// works with caller 1
469+
assert_ok!(Authority::authorize_call(
470+
Origin::root(),
471+
Box::new(call.clone()),
472+
Some(1)
473+
));
474+
// caller 2 is not permitted to trigger the call
475+
assert_noop!(
476+
Authority::trigger_call(Origin::signed(2), hash, call_weight_bound),
477+
Error::<Runtime>::TriggerCallNotPermitted
478+
);
479+
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));
480+
481+
// caller 1 triggering the call
482+
assert_ok!(Authority::trigger_call(Origin::signed(1), hash, call_weight_bound));
483+
assert_eq!(Authority::saved_calls(&hash), None);
484+
System::assert_has_event(mock::Event::Authority(Event::TriggeredCallBy(hash, 1)));
485+
System::assert_last_event(mock::Event::Authority(Event::Dispatched(Ok(()))));
486+
});
487+
}
488+
489+
#[test]
490+
fn remove_authorized_call_works() {
491+
ExtBuilder::default().build().execute_with(|| {
492+
run_to_block(1);
493+
let ensure_root_call = Call::System(frame_system::Call::fill_block(Perbill::one()));
494+
let call = Call::Authority(authority::Call::dispatch_as(
495+
MockAsOriginId::Root,
496+
Box::new(ensure_root_call),
497+
));
498+
let hash = <Runtime as frame_system::Config>::Hashing::hash_of(&call);
499+
500+
assert_noop!(
501+
Authority::remove_authorized_call(Origin::root(), hash),
502+
Error::<Runtime>::CallNotAuthorized
503+
);
504+
505+
assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
506+
assert_noop!(
507+
Authority::remove_authorized_call(Origin::signed(1), hash),
508+
Error::<Runtime>::CallNotAuthorized
509+
);
510+
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), None)));
511+
assert_ok!(Authority::remove_authorized_call(Origin::root(), hash));
512+
assert_eq!(Authority::saved_calls(&hash), None);
513+
514+
assert_ok!(Authority::authorize_call(
515+
Origin::root(),
516+
Box::new(call.clone()),
517+
Some(1)
518+
));
519+
assert_ok!(Authority::remove_authorized_call(Origin::root(), hash));
520+
assert_eq!(Authority::saved_calls(&hash), None);
521+
522+
assert_ok!(Authority::authorize_call(
523+
Origin::root(),
524+
Box::new(call.clone()),
525+
Some(1)
526+
));
527+
assert_noop!(
528+
Authority::remove_authorized_call(Origin::signed(2), hash),
529+
Error::<Runtime>::CallNotAuthorized
530+
);
531+
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));
532+
assert_ok!(Authority::remove_authorized_call(Origin::signed(1), hash));
533+
assert_eq!(Authority::saved_calls(&hash), None);
534+
});
535+
}

authority/src/weights.rs

+27-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! Autogenerated weights for orml_authority
22
//!
3-
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
4-
//! DATE: 2021-05-04, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
3+
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
4+
//! DATE: 2021-09-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
55
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
66
77
// Executed Command:
8-
// /Users/xiliangchen/projects/acala/target/release/acala
8+
// /Users/ermal/Acala/target/release/acala
99
// benchmark
1010
// --chain=dev
1111
// --steps=50
@@ -15,9 +15,8 @@
1515
// --execution=wasm
1616
// --wasm-execution=compiled
1717
// --heap-pages=4096
18+
// --template=../templates/orml-weight-template.hbs
1819
// --output=./authority/src/weights.rs
19-
// --template
20-
// ../templates/orml-weight-template.hbs
2120

2221

2322
#![cfg_attr(rustfmt, rustfmt_skip)]
@@ -36,36 +35,53 @@ pub trait WeightInfo {
3635
fn fast_track_scheduled_dispatch() -> Weight;
3736
fn delay_scheduled_dispatch() -> Weight;
3837
fn cancel_scheduled_dispatch() -> Weight;
38+
fn authorize_call() -> Weight;
39+
fn remove_authorized_call() -> Weight;
40+
fn trigger_call() -> Weight;
3941
}
4042

4143
/// Default weights.
4244
impl WeightInfo for () {
4345
fn dispatch_as() -> Weight {
44-
(10_000_000 as Weight)
46+
(12_000_000 as Weight)
4547
}
4648
fn schedule_dispatch_without_delay() -> Weight {
47-
(28_000_000 as Weight)
49+
(30_000_000 as Weight)
4850
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
4951
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
5052
}
5153
fn schedule_dispatch_with_delay() -> Weight {
52-
(29_000_000 as Weight)
54+
(32_000_000 as Weight)
5355
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
5456
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
5557
}
5658
fn fast_track_scheduled_dispatch() -> Weight {
57-
(36_000_000 as Weight)
59+
(42_000_000 as Weight)
5860
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
5961
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
6062
}
6163
fn delay_scheduled_dispatch() -> Weight {
62-
(36_000_000 as Weight)
64+
(42_000_000 as Weight)
6365
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
6466
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
6567
}
6668
fn cancel_scheduled_dispatch() -> Weight {
67-
(24_000_000 as Weight)
69+
(29_000_000 as Weight)
6870
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
6971
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
7072
}
73+
fn authorize_call() -> Weight {
74+
(14_000_000 as Weight)
75+
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
76+
}
77+
fn remove_authorized_call() -> Weight {
78+
(16_000_000 as Weight)
79+
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
80+
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
81+
}
82+
fn trigger_call() -> Weight {
83+
(29_000_000 as Weight)
84+
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
85+
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
86+
}
7187
}

0 commit comments

Comments
 (0)