Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prevent nested DelayedOrigin #845

Merged
merged 6 commits into from
Nov 22, 2022
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
4 changes: 2 additions & 2 deletions authority/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive"
serde = { version = "1.0.145", optional = true }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }

sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.31" }

[dev-dependencies]
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
pallet-scheduler = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
pallet-preimage = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }

Expand Down
74 changes: 67 additions & 7 deletions authority/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
//! Two functionalities are provided by this module:
//! - schedule a dispatchable
//! - dispatch method with on behalf of other origins
//!
//! NOTE:
//!
//! In order to derive a feasible max encoded len for `DelayedOrigin`, it is
//! assumed that there are no nested `DelayedOrigin` in `OriginCaller`.
//! In practice, this means there should not be nested `schedule_dispatch`.
//! Otherwise the proof size estimation may not be accurate.

#![cfg_attr(not(feature = "std"), no_std)]
// Disable the following three lints since they originate from an external macro
Expand All @@ -32,6 +39,7 @@ use frame_support::{
};
use frame_system::{pallet_prelude::*, EnsureRoot, EnsureSigned};
use scale_info::TypeInfo;
use sp_core::defer;
use sp_runtime::{
traits::{CheckedSub, Dispatchable, Hash, Saturating},
ArithmeticError, DispatchError, DispatchResult, Either, RuntimeDebug,
Expand All @@ -45,12 +53,64 @@ mod weights;
pub use weights::WeightInfo;

/// A delayed origin. Can only be dispatched via `dispatch_as` with a delay.
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)]
pub struct DelayedOrigin<BlockNumber, PalletsOrigin> {
/// Number of blocks that this call have been delayed.
pub delay: BlockNumber,
pub(crate) delay: BlockNumber,
/// The initial origin.
pub origin: Box<PalletsOrigin>,
pub(crate) origin: Box<PalletsOrigin>,
}

#[cfg(feature = "std")]
mod helper {
use std::cell::RefCell;

thread_local! {
static NESTED_MAX_ENCODED_LEN: RefCell<bool> = RefCell::new(false);
}

pub fn set_nested_max_encoded_len(val: bool) {
NESTED_MAX_ENCODED_LEN.with(|v| *v.borrow_mut() = val);
}

pub fn nested_max_encoded_len() -> bool {
NESTED_MAX_ENCODED_LEN.with(|v| *v.borrow())
}
}

#[cfg(not(feature = "std"))]
mod helper {
static mut NESTED_MAX_ENCODED_LEN: bool = false;

pub fn set_nested_max_encoded_len(val: bool) {
unsafe {
NESTED_MAX_ENCODED_LEN = val;
}
}

pub fn nested_max_encoded_len() -> bool {
unsafe { NESTED_MAX_ENCODED_LEN }
}
}

// Manual implementation to break recursive calls of `MaxEncodedLen` as the
// implementation of `PalletsOrigin::max_encoded_len` will also call
// `MaxEncodedLen` on `DelayedOrigin`. This is only safe if there are no nested
// `DelayedOrigin`. It is only possible to construct a `DelayedOrigin` via
// `schedule_dispatch` which is a protected call only accessible via governance.
impl<BlockNumber: MaxEncodedLen, PalletsOrigin: MaxEncodedLen> MaxEncodedLen
for DelayedOrigin<BlockNumber, PalletsOrigin>
{
fn max_encoded_len() -> usize {
if helper::nested_max_encoded_len() {
return 0;
}

helper::set_nested_max_encoded_len(true);
defer!(helper::set_nested_max_encoded_len(false));

BlockNumber::max_encoded_len() + PalletsOrigin::max_encoded_len()
}
}

/// Ensure the origin have a minimum amount of delay.
Expand Down Expand Up @@ -99,10 +159,10 @@ pub trait AuthorityConfig<Origin, PalletsOrigin, BlockNumber> {
new_delay: BlockNumber,
) -> DispatchResult;
/// Check if the `origin` is allow to delay a scheduled task that
/// initially created by `inital_origin`.
/// initially created by `initial_origin`.
fn check_delay_schedule(origin: Origin, initial_origin: &PalletsOrigin) -> DispatchResult;
/// Check if the `origin` is allow to cancel a scheduled task that
/// initially created by `inital_origin`.
/// initially created by `initial_origin`.
fn check_cancel_schedule(origin: Origin, initial_origin: &PalletsOrigin) -> DispatchResult;
}

Expand Down Expand Up @@ -396,12 +456,12 @@ pub mod module {

#[pallet::weight(T::WeightInfo::remove_authorized_call())]
pub fn remove_authorized_call(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
let root_or_sigend =
let root_or_signed =
EitherOfDiverse::<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 {
match root_or_signed {
Either::Left(_) => {} // root, do nothing
Either::Right(who) => {
// signed, ensure it's the caller
Expand Down
7 changes: 7 additions & 0 deletions authority/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![cfg(test)]

use super::*;
use codec::MaxEncodedLen;
use frame_support::{
assert_noop, assert_ok,
dispatch::DispatchErrorWithPostInfo,
Expand Down Expand Up @@ -691,3 +692,9 @@ fn trigger_old_call_should_be_free_and_operational() {
);
});
}

#[test]
fn origin_max_encoded_len_works() {
assert_eq!(DelayedOrigin::<u32, OriginCaller>::max_encoded_len(), 22);
assert_eq!(OriginCaller::max_encoded_len(), 27);
}