Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
80 changes: 77 additions & 3 deletions authority/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ use frame_support::{
},
weights::GetDispatchInfo,
};
use frame_system::pallet_prelude::*;
use frame_system::{pallet_prelude::*, EnsureOneOf, EnsureRoot, EnsureSigned};
use sp_runtime::{
traits::{CheckedSub, Dispatchable, Saturating},
ArithmeticError, DispatchError, DispatchResult, RuntimeDebug,
traits::{CheckedSub, Dispatchable, Hash, Saturating},
ArithmeticError, DispatchError, DispatchResult, Either, RuntimeDebug,
};
use sp_std::prelude::*;

Expand Down Expand Up @@ -172,6 +172,12 @@ pub mod module {
FailedToFastTrack,
/// Failed to delay a task.
FailedToDelay,
/// Call is not authorized.
CallNotAuthorized,
/// Triggering the call is not permitted.
TriggerCallNotPermitted,
/// Call weight bound is wrong.
WrongCallWeightBound,
}

#[pallet::event]
Expand All @@ -187,12 +193,22 @@ 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>),
/// An authorized call was removed. \[hash\]
RemovedAuthorizedCall(T::Hash),
/// An authorized call was 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<_, Identity, T::Hash, (CallOf<T>, Option<T::AccountId>), OptionQuery>;

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

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

#[pallet::weight(T::WeightInfo::authorize_call())]
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()));
Self::deposit_event(Event::AuthorizedCall(hash, caller));
Ok(())
}

#[pallet::weight(T::WeightInfo::remove_authorized_call())]
pub fn remove_authorized_call(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
let root_or_sigend =
EnsureOneOf::<T::AccountId, EnsureRoot<T::AccountId>, EnsureSigned<T::AccountId>>::ensure_origin(
origin,
)?;

SavedCalls::<T>::try_mutate_exists(hash, |maybe_call| {
let (_, maybe_caller) = maybe_call.take().ok_or(Error::<T>::CallNotAuthorized)?;
match root_or_sigend {
Either::Left(_) => {} // root, do nothing
Either::Right(who) => {
// signed, ensure it's the caller
let caller = maybe_caller.ok_or(Error::<T>::CallNotAuthorized)?;
ensure!(who == caller, Error::<T>::CallNotAuthorized);
}
}
Self::deposit_event(Event::RemovedAuthorizedCall(hash));
Ok(())
})
}

#[pallet::weight(T::WeightInfo::trigger_call().saturating_add(*call_weight_bound))]
pub fn trigger_call(
origin: OriginFor<T>,
hash: T::Hash,
#[pallet::compact] call_weight_bound: Weight,
) -> DispatchResult {
let who = ensure_signed(origin)?;
SavedCalls::<T>::try_mutate_exists(hash, |maybe_call| {
let (call, maybe_caller) = maybe_call.take().ok_or(Error::<T>::CallNotAuthorized)?;
if let Some(caller) = maybe_caller {
ensure!(who == caller, Error::<T>::TriggerCallNotPermitted);
}
ensure!(
call_weight_bound >= call.get_dispatch_info().weight,
Error::<T>::WrongCallWeightBound
);
let result = call.dispatch(OriginFor::<T>::root());
Copy link
Contributor

@apopiak apopiak Sep 14, 2021

Choose a reason for hiding this comment

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

you skip weighing the call
you'll probably want to pass a weight as param and then ensure that the executed call has lower or equal weight to it

Copy link
Member Author

Choose a reason for hiding this comment

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

you skip weighing the call
you'll probably want to pass a weight as param and then ensure that the executed call has lower or equal weight to it

pushed an update to handle call post_info

Self::deposit_event(Event::TriggeredCallBy(hash, who));
Self::deposit_event(Event::Dispatched(result.map(|_| ()).map_err(|e| e.error)));
Ok(())
})
}
}
}
129 changes: 129 additions & 0 deletions authority/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,132 @@ 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);

let call_weight_bound = call.get_dispatch_info().weight;

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

assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));

// wrong call weight bound
assert_noop!(
Authority::trigger_call(Origin::signed(1), hash, call_weight_bound - 1),
Error::<Runtime>::WrongCallWeightBound
);

// works without caller
assert_ok!(Authority::trigger_call(Origin::signed(1), hash, call_weight_bound));
assert_eq!(Authority::saved_calls(&hash), None);
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, call_weight_bound),
Error::<Runtime>::TriggerCallNotPermitted
);
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));

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

#[test]
fn remove_authorized_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);

assert_noop!(
Authority::remove_authorized_call(Origin::root(), hash),
Error::<Runtime>::CallNotAuthorized
);

assert_ok!(Authority::authorize_call(Origin::root(), Box::new(call.clone()), None));
assert_noop!(
Authority::remove_authorized_call(Origin::signed(1), hash),
Error::<Runtime>::CallNotAuthorized
);
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), None)));
assert_ok!(Authority::remove_authorized_call(Origin::root(), hash));
assert_eq!(Authority::saved_calls(&hash), None);

assert_ok!(Authority::authorize_call(
Origin::root(),
Box::new(call.clone()),
Some(1)
));
assert_ok!(Authority::remove_authorized_call(Origin::root(), hash));
assert_eq!(Authority::saved_calls(&hash), None);

assert_ok!(Authority::authorize_call(
Origin::root(),
Box::new(call.clone()),
Some(1)
));
assert_noop!(
Authority::remove_authorized_call(Origin::signed(2), hash),
Error::<Runtime>::CallNotAuthorized
);
assert_eq!(Authority::saved_calls(&hash), Some((call.clone(), Some(1))));
assert_ok!(Authority::remove_authorized_call(Origin::signed(1), hash));
assert_eq!(Authority::saved_calls(&hash), None);
});
}
38 changes: 27 additions & 11 deletions authority/src/weights.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Autogenerated weights for orml_authority
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
//! DATE: 2021-05-04, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2021-09-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128

// Executed Command:
// /Users/xiliangchen/projects/acala/target/release/acala
// /Users/ermal/Acala/target/release/acala
// benchmark
// --chain=dev
// --steps=50
Expand All @@ -15,9 +15,8 @@
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --template=../templates/orml-weight-template.hbs
// --output=./authority/src/weights.rs
// --template
// ../templates/orml-weight-template.hbs


#![cfg_attr(rustfmt, rustfmt_skip)]
Expand All @@ -36,36 +35,53 @@ pub trait WeightInfo {
fn fast_track_scheduled_dispatch() -> Weight;
fn delay_scheduled_dispatch() -> Weight;
fn cancel_scheduled_dispatch() -> Weight;
fn authorize_call() -> Weight;
fn remove_authorized_call() -> Weight;
fn trigger_call() -> Weight;
}

/// Default weights.
impl WeightInfo for () {
fn dispatch_as() -> Weight {
(10_000_000 as Weight)
(12_000_000 as Weight)
}
fn schedule_dispatch_without_delay() -> Weight {
(28_000_000 as Weight)
(30_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
fn schedule_dispatch_with_delay() -> Weight {
(29_000_000 as Weight)
(32_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
fn fast_track_scheduled_dispatch() -> Weight {
(36_000_000 as Weight)
(42_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
fn delay_scheduled_dispatch() -> Weight {
(36_000_000 as Weight)
(42_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
fn cancel_scheduled_dispatch() -> Weight {
(24_000_000 as Weight)
(29_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
fn authorize_call() -> Weight {
(14_000_000 as Weight)
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
fn remove_authorized_call() -> Weight {
(16_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
fn trigger_call() -> Weight {
(29_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
}