diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9595ef424d8c1..e7842e5c4ba3b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -109,7 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 257, + spec_version: 258, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -216,6 +216,9 @@ parameter_types! { // Additional storage item size of 33 bytes. pub const ProxyDepositFactor: Balance = deposit(0, 33); pub const MaxProxies: u16 = 32; + pub const AnnouncementDepositBase: Balance = deposit(1, 8); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; } /// The type used to represent the kinds of proxying allowed. @@ -261,7 +264,11 @@ impl pallet_proxy::Trait for Runtime { type ProxyDepositBase = ProxyDepositBase; type ProxyDepositFactor = ProxyDepositFactor; type MaxProxies = MaxProxies; - type WeightInfo = (); + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; } parameter_types! { diff --git a/bin/node/runtime/src/weights/mod.rs b/bin/node/runtime/src/weights/mod.rs index 0e078e7ac0859..322fb8886c0f4 100644 --- a/bin/node/runtime/src/weights/mod.rs +++ b/bin/node/runtime/src/weights/mod.rs @@ -18,5 +18,6 @@ pub mod frame_system; pub mod pallet_balances; pub mod pallet_democracy; +pub mod pallet_proxy; pub mod pallet_timestamp; pub mod pallet_utility; diff --git a/bin/node/runtime/src/weights/pallet_balances.rs b/bin/node/runtime/src/weights/pallet_balances.rs index 21a90a97e63a1..bcbc4ced6ef56 100644 --- a/bin/node/runtime/src/weights/pallet_balances.rs +++ b/bin/node/runtime/src/weights/pallet_balances.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Weights for the Balances Pallet +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc5 use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; diff --git a/bin/node/runtime/src/weights/pallet_proxy.rs b/bin/node/runtime/src/weights/pallet_proxy.rs new file mode 100644 index 0000000000000..92c43cd4853a2 --- /dev/null +++ b/bin/node/runtime/src/weights/pallet_proxy.rs @@ -0,0 +1,85 @@ +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc5 + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +pub struct WeightInfo; +impl pallet_proxy::WeightInfo for WeightInfo { + fn proxy(p: u32, ) -> Weight { + (26127000 as Weight) + .saturating_add((214000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + } + fn proxy_announced(a: u32, p: u32, ) -> Weight { + (55405000 as Weight) + .saturating_add((774000 as Weight).saturating_mul(a as Weight)) + .saturating_add((209000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn remove_announcement(a: u32, p: u32, ) -> Weight { + (35879000 as Weight) + .saturating_add((783000 as Weight).saturating_mul(a as Weight)) + .saturating_add((20000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn reject_announcement(a: u32, p: u32, ) -> Weight { + (36097000 as Weight) + .saturating_add((780000 as Weight).saturating_mul(a as Weight)) + .saturating_add((12000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn announce(a: u32, p: u32, ) -> Weight { + (53769000 as Weight) + .saturating_add((675000 as Weight).saturating_mul(a as Weight)) + .saturating_add((214000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn add_proxy(p: u32, ) -> Weight { + (36082000 as Weight) + .saturating_add((234000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_proxy(p: u32, ) -> Weight { + (32885000 as Weight) + .saturating_add((267000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_proxies(p: u32, ) -> Weight { + (31735000 as Weight) + .saturating_add((215000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn anonymous(p: u32, ) -> Weight { + (50907000 as Weight) + .saturating_add((61000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn kill_anonymous(p: u32, ) -> Weight { + (33926000 as Weight) + .saturating_add((208000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } +} diff --git a/frame/proxy/src/benchmarking.rs b/frame/proxy/src/benchmarking.rs index f68a2c3a4cd86..5f1d79741dd8e 100644 --- a/frame/proxy/src/benchmarking.rs +++ b/frame/proxy/src/benchmarking.rs @@ -20,13 +20,21 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_system::RawOrigin; +use frame_system::{RawOrigin, EventRecord}; use frame_benchmarking::{benchmarks, account, whitelisted_caller}; use sp_runtime::traits::Bounded; use crate::Module as Proxy; const SEED: u32 = 0; +fn assert_last_event(generic_event: ::Event) { + let events = frame_system::Module::::events(); + let system_event: ::Event = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + fn add_proxies(n: u32, maybe_who: Option) -> Result<(), &'static str> { let caller = maybe_who.unwrap_or_else(|| whitelisted_caller()); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); @@ -34,7 +42,38 @@ fn add_proxies(n: u32, maybe_who: Option) -> Result<(), Proxy::::add_proxy( RawOrigin::Signed(caller.clone()).into(), account("target", i, SEED), - T::ProxyType::default() + T::ProxyType::default(), + T::BlockNumber::zero(), + )?; + } + Ok(()) +} + +fn add_announcements( + n: u32, + maybe_who: Option, + maybe_real: Option +) -> Result<(), &'static str> { + let caller = maybe_who.unwrap_or_else(|| account("caller", 0, SEED)); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let real = if let Some(real) = maybe_real { + real + } else { + let real = account("real", 0, SEED); + T::Currency::make_free_balance_be(&real, BalanceOf::::max_value()); + Proxy::::add_proxy( + RawOrigin::Signed(real.clone()).into(), + caller.clone(), + T::ProxyType::default(), + T::BlockNumber::zero(), + )?; + real + }; + for _ in 0..n { + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real.clone(), + T::CallHasher::hash_of(&("add_announcement", n)), )?; } Ok(()) @@ -49,43 +88,171 @@ benchmarks! { let p in ...; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let call: ::Call = frame_system::Call::::remark(vec![]).into(); }: _(RawOrigin::Signed(caller), real, Some(T::ProxyType::default()), Box::new(call)) + verify { + assert_last_event::(RawEvent::ProxyExecuted(Ok(())).into()) + } + + proxy_announced { + let a in 0 .. T::MaxPending::get() - 1; + let p in ...; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("anonymous", 0, SEED); + let delegate: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&delegate, BalanceOf::::max_value()); + // ... and "real" is the traditional caller. This is not a typo. + let real: T::AccountId = whitelisted_caller(); + let call: ::Call = frame_system::Call::::remark(vec![]).into(); + Proxy::::announce( + RawOrigin::Signed(delegate.clone()).into(), + real.clone(), + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(delegate.clone()), None)?; + }: _(RawOrigin::Signed(caller), delegate, real, Some(T::ProxyType::default()), Box::new(call)) + verify { + assert_last_event::(RawEvent::ProxyExecuted(Ok(())).into()) + } + + remove_announcement { + let a in 0 .. T::MaxPending::get() - 1; + let p in ...; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + // ... and "real" is the traditional caller. This is not a typo. + let real: T::AccountId = whitelisted_caller(); + let call: ::Call = frame_system::Call::::remark(vec![]).into(); + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real.clone(), + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(caller.clone()), None)?; + }: _(RawOrigin::Signed(caller.clone()), real, T::CallHasher::hash_of(&call)) + verify { + let (announcements, _) = Announcements::::get(&caller); + assert_eq!(announcements.len() as u32, a); + } + + reject_announcement { + let a in 0 .. T::MaxPending::get() - 1; + let p in ...; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + // ... and "real" is the traditional caller. This is not a typo. + let real: T::AccountId = whitelisted_caller(); + let call: ::Call = frame_system::Call::::remark(vec![]).into(); + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real.clone(), + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(caller.clone()), None)?; + }: _(RawOrigin::Signed(real), caller.clone(), T::CallHasher::hash_of(&call)) + verify { + let (announcements, _) = Announcements::::get(&caller); + assert_eq!(announcements.len() as u32, a); + } + + announce { + let a in 0 .. T::MaxPending::get() - 1; + let p in ...; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + // ... and "real" is the traditional caller. This is not a typo. + let real: T::AccountId = whitelisted_caller(); + add_announcements::(a, Some(caller.clone()), None)?; + let call: ::Call = frame_system::Call::::remark(vec![]).into(); + let call_hash = T::CallHasher::hash_of(&call); + }: _(RawOrigin::Signed(caller.clone()), real.clone(), call_hash) + verify { + assert_last_event::(RawEvent::Announced(real, caller, call_hash).into()); + } add_proxy { let p in ...; let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), account("target", T::MaxProxies::get().into(), SEED), T::ProxyType::default()) + }: _( + RawOrigin::Signed(caller.clone()), + account("target", T::MaxProxies::get().into(), SEED), + T::ProxyType::default(), + T::BlockNumber::zero() + ) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, p + 1); + } remove_proxy { let p in ...; let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), account("target", 0, SEED), T::ProxyType::default()) + }: _( + RawOrigin::Signed(caller.clone()), + account("target", 0, SEED), + T::ProxyType::default(), + T::BlockNumber::zero() + ) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, p - 1); + } remove_proxies { let p in ...; let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller)) + }: _(RawOrigin::Signed(caller.clone())) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, 0); + } anonymous { let p in ...; - }: _(RawOrigin::Signed(whitelisted_caller()), T::ProxyType::default(), 0) + let caller: T::AccountId = whitelisted_caller(); + }: _( + RawOrigin::Signed(caller.clone()), + T::ProxyType::default(), + T::BlockNumber::zero(), + 0 + ) + verify { + let anon_account = Module::::anonymous_account(&caller, &T::ProxyType::default(), 0, None); + assert_last_event::(RawEvent::AnonymousCreated( + anon_account, + caller, + T::ProxyType::default(), + 0, + ).into()); + } kill_anonymous { let p in 0 .. (T::MaxProxies::get() - 2).into(); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - Module::::anonymous(RawOrigin::Signed(whitelisted_caller()).into(), T::ProxyType::default(), 0)?; + Module::::anonymous( + RawOrigin::Signed(whitelisted_caller()).into(), + T::ProxyType::default(), + T::BlockNumber::zero(), + 0 + )?; let height = system::Module::::block_number(); let ext_index = system::Module::::extrinsic_index().unwrap_or(0); let anon = Module::::anonymous_account(&caller, &T::ProxyType::default(), 0, None); add_proxies::(p, Some(anon.clone()))?; - - }: _(RawOrigin::Signed(anon), caller, T::ProxyType::default(), 0, height, ext_index) + ensure!(Proxies::::contains_key(&anon), "anon proxy not created"); + }: _(RawOrigin::Signed(anon.clone()), caller.clone(), T::ProxyType::default(), 0, height, ext_index) + verify { + assert!(!Proxies::::contains_key(&anon)); + } } #[cfg(test)] @@ -98,6 +265,10 @@ mod tests { fn test_benchmarks() { new_test_ext().execute_with(|| { assert_ok!(test_benchmark_proxy::()); + assert_ok!(test_benchmark_proxy_announced::()); + assert_ok!(test_benchmark_remove_announcement::()); + assert_ok!(test_benchmark_reject_announcement::()); + assert_ok!(test_benchmark_announce::()); assert_ok!(test_benchmark_add_proxy::()); assert_ok!(test_benchmark_remove_proxy::()); assert_ok!(test_benchmark_remove_proxies::()); diff --git a/frame/proxy/src/default_weight.rs b/frame/proxy/src/default_weight.rs new file mode 100644 index 0000000000000..183c0b81c8a07 --- /dev/null +++ b/frame/proxy/src/default_weight.rs @@ -0,0 +1,84 @@ +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc5 + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +impl crate::WeightInfo for () { + fn proxy(p: u32, ) -> Weight { + (26127000 as Weight) + .saturating_add((214000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + } + fn proxy_announced(a: u32, p: u32, ) -> Weight { + (55405000 as Weight) + .saturating_add((774000 as Weight).saturating_mul(a as Weight)) + .saturating_add((209000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn remove_announcement(a: u32, p: u32, ) -> Weight { + (35879000 as Weight) + .saturating_add((783000 as Weight).saturating_mul(a as Weight)) + .saturating_add((20000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn reject_announcement(a: u32, p: u32, ) -> Weight { + (36097000 as Weight) + .saturating_add((780000 as Weight).saturating_mul(a as Weight)) + .saturating_add((12000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn announce(a: u32, p: u32, ) -> Weight { + (53769000 as Weight) + .saturating_add((675000 as Weight).saturating_mul(a as Weight)) + .saturating_add((214000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn add_proxy(p: u32, ) -> Weight { + (36082000 as Weight) + .saturating_add((234000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_proxy(p: u32, ) -> Weight { + (32885000 as Weight) + .saturating_add((267000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn remove_proxies(p: u32, ) -> Weight { + (31735000 as Weight) + .saturating_add((215000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn anonymous(p: u32, ) -> Weight { + (50907000 as Weight) + .saturating_add((61000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn kill_anonymous(p: u32, ) -> Weight { + (33926000 as Weight) + .saturating_add((208000 as Weight).saturating_mul(p as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } +} diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index ec43007827667..5a852ea9f5314 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -19,6 +19,10 @@ //! A module allowing accounts to give permission to other accounts to dispatch types of calls from //! their signed origin. //! +//! The accounts to which permission is delegated may be requied to announce the action that they +//! wish to execute some duration prior to execution happens. In this case, the target account may +//! reject the announcement and in doing so, veto the execution. +//! //! - [`proxy::Trait`](./trait.Trait.html) //! - [`Call`](./enum.Call.html) //! @@ -37,23 +41,27 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; use sp_io::hashing::blake2_256; -use sp_runtime::{DispatchResult, traits::{Dispatchable, Zero}}; -use sp_runtime::traits::Member; +use sp_runtime::{DispatchResult, traits::{Dispatchable, Zero, Hash, Member, Saturating}}; use frame_support::{ - decl_module, decl_event, decl_error, decl_storage, Parameter, ensure, traits::{ - Get, ReservableCurrency, Currency, InstanceFilter, - OriginTrait, IsType, - }, weights::{Weight, GetDispatchInfo, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}}, - dispatch::{PostDispatchInfo, IsSubType}, + decl_module, decl_event, decl_error, decl_storage, Parameter, ensure, RuntimeDebug, traits::{ + Get, ReservableCurrency, Currency, InstanceFilter, OriginTrait, IsType, + }, weights::{Weight, GetDispatchInfo}, + dispatch::{PostDispatchInfo, IsSubType}, storage::IterableStorageMap, }; use frame_system::{self as system, ensure_signed}; +use frame_support::dispatch::DispatchError; mod tests; mod benchmarking; +mod default_weight; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub trait WeightInfo { + fn proxy_announced(a: u32, p: u32, ) -> Weight; + fn remove_announcement(a: u32, p: u32, ) -> Weight; + fn reject_announcement(a: u32, p: u32, ) -> Weight; + fn announce(a: u32, p: u32, ) -> Weight; fn proxy(p: u32, ) -> Weight; fn add_proxy(p: u32, ) -> Weight; fn remove_proxy(p: u32, ) -> Weight; @@ -62,15 +70,6 @@ pub trait WeightInfo { fn kill_anonymous(p: u32, ) -> Weight; } -impl WeightInfo for () { - fn proxy(_p: u32, ) -> Weight { 1_000_000_000 } - fn add_proxy(_p: u32, ) -> Weight { 1_000_000_000 } - fn remove_proxy(_p: u32, ) -> Weight { 1_000_000_000 } - fn remove_proxies(_p: u32, ) -> Weight { 1_000_000_000 } - fn anonymous(_p: u32, ) -> Weight { 1_000_000_000 } - fn kill_anonymous(_p: u32, ) -> Weight { 1_000_000_000 } -} - /// Configuration trait. pub trait Trait: frame_system::Trait { /// The overarching event type. @@ -108,19 +107,67 @@ pub trait Trait: frame_system::Trait { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// The maximum amount of time-delayed announcements that are allowed to be pending. + type MaxPending: Get; + + /// The type of hash used for hashing the call. + type CallHasher: Hash; + + /// The base amount of currency needed to reserve for creating an announcement. + /// + /// This is held when a new storage item holding a `Balance` is created (typically 16 bytes). + type AnnouncementDepositBase: Get>; + + /// The amount of currency needed per announcement made. + /// + /// This is held for adding an `AccountId`, `Hash` and `BlockNumber` (typically 68 bytes) + /// into a pre-existing storage value. + type AnnouncementDepositFactor: Get>; +} + +/// The parameters under which a particular account has a proxy relationship with some other +/// account. +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] +pub struct ProxyDefinition { + /// The account which may act on behalf of another. + delegate: AccountId, + /// A value defining the subset of calls that it is allowed to make. + proxy_type: ProxyType, + /// The number of blocks that an announcement must be in place for before the corresponding call + /// may be dispatched. If zero, then no announcement is needed. + delay: BlockNumber, +} + +/// Details surrounding a specific instance of an announcement to make a call. +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug)] +pub struct Announcement { + /// The account which made the announcement. + real: AccountId, + /// The hash of the call to be made. + call_hash: Hash, + /// The height at which the announcement was made. + height: BlockNumber, } +type CallHashOf = <::CallHasher as Hash>::Output; + decl_storage! { trait Store for Module as Proxy { /// The set of account proxies. Maps the account which has delegated to the accounts /// which are being delegated to, together with the amount held on deposit. - pub Proxies: map hasher(twox_64_concat) T::AccountId => (Vec<(T::AccountId, T::ProxyType)>, BalanceOf); + pub Proxies: map hasher(twox_64_concat) T::AccountId + => (Vec>, BalanceOf); + + /// The announcements made by the proxy (key). + pub Announcements: map hasher(twox_64_concat) T::AccountId + => (Vec, T::BlockNumber>>, BalanceOf); } } decl_error! { pub enum Error for Module { - /// There are too many proxies registered. + /// There are too many proxies registered or too many announcements pending. TooMany, /// Proxy registration not found. NotFound, @@ -132,6 +179,8 @@ decl_error! { Duplicate, /// Call may not be made by proxy because it may escalate its privileges. NoPermission, + /// Announcement, if made at all, was made too recently. + Unannounced, } } @@ -139,13 +188,16 @@ decl_event! { /// Events type. pub enum Event where AccountId = ::AccountId, - ProxyType = ::ProxyType + ProxyType = ::ProxyType, + Hash = CallHashOf, { /// A proxy was executed correctly, with the given [result]. ProxyExecuted(DispatchResult), /// Anonymous account has been created by new proxy with given /// disambiguation index and proxy type. [anonymous, who, proxy_type, disambiguation_index] AnonymousCreated(AccountId, AccountId, ProxyType, u16), + /// An announcement was placed to make a call in the future. [real, proxy, call_hash] + Announced(AccountId, AccountId, Hash), } } @@ -165,9 +217,36 @@ decl_module! { /// The maximum amount of proxies allowed for a single account. const MaxProxies: u16 = T::MaxProxies::get(); + /// `MaxPending` metadata shadow. + const MaxPending: u32 = T::MaxPending::get(); + + /// `AnnouncementDepositBase` metadata shadow. + const AnnouncementDepositBase: BalanceOf = T::AnnouncementDepositBase::get(); + + /// `AnnouncementDepositFactor` metadata shadow. + const AnnouncementDepositFactor: BalanceOf = T::AnnouncementDepositFactor::get(); + + fn on_runtime_upgrade() -> Weight { + Proxies::::translate::<(Vec<(T::AccountId, T::ProxyType)>, BalanceOf), _>( + |_, (targets, deposit)| Some(( + targets.into_iter() + .map(|(a, t)| ProxyDefinition { + delegate: a, + proxy_type: t, + delay: Zero::zero(), + }) + .collect::>(), + deposit, + )) + ); + T::MaximumBlockWeight::get() + } + /// Dispatch the given `call` from an account that the sender is authorised for through /// `add_proxy`. /// + /// Removes any corresponding announcement(s). + /// /// The dispatch origin for this call must be _Signed_. /// /// Parameters: @@ -176,43 +255,24 @@ decl_module! { /// - `call`: The call to be made by the `real` account. /// /// # - /// P is the number of proxies the user has - /// - Base weight: 19.87 + .141 * P µs - /// - DB weight: 1 storage read. - /// - Plus the weight of the `call` + /// Weight is a function of the number of proxies the user has (P). /// # #[weight = { let di = call.get_dispatch_info(); - (T::DbWeight::get().reads(1) - .saturating_add(di.weight) - .saturating_add(20 * WEIGHT_PER_MICROS) - .saturating_add((140 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())), + (T::WeightInfo::proxy(T::MaxProxies::get().into()) + .saturating_add(di.weight), di.class) }] fn proxy(origin, real: T::AccountId, force_proxy_type: Option, - call: Box<::Call> + call: Box<::Call>, ) { let who = ensure_signed(origin)?; - let (_, proxy_type) = Proxies::::get(&real).0.into_iter() - .find(|x| &x.0 == &who && force_proxy_type.as_ref().map_or(true, |y| &x.1 == y)) - .ok_or(Error::::NotProxy)?; + let def = Self::find_proxy(&real, &who, force_proxy_type)?; + ensure!(def.delay.is_zero(), Error::::Unannounced); - // This is a freshly authenticated new account, the origin restrictions doesn't apply. - let mut origin: T::Origin = frame_system::RawOrigin::Signed(real).into(); - origin.add_filter(move |c: &::Call| { - let c = ::Call::from_ref(c); - match c.is_sub_type() { - Some(Call::add_proxy(_, ref pt)) | Some(Call::remove_proxy(_, ref pt)) - if !proxy_type.is_superset(&pt) => false, - Some(Call::remove_proxies(..)) | Some(Call::kill_anonymous(..)) - if proxy_type != T::ProxyType::default() => false, - _ => proxy_type.filter(c) - } - }); - let e = call.dispatch(origin); - Self::deposit_event(RawEvent::ProxyExecuted(e.map(|_| ()).map_err(|e| e.error))); + Self::do_proxy(def, real, *call); } /// Register a proxy account for the sender that is able to make calls on its behalf. @@ -224,21 +284,20 @@ decl_module! { /// - `proxy_type`: The permissions allowed for this proxy account. /// /// # - /// P is the number of proxies the user has - /// - Base weight: 17.48 + .176 * P µs - /// - DB weight: 1 storage read and write. + /// Weight is a function of the number of proxies the user has (P). /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(18 * WEIGHT_PER_MICROS) - .saturating_add((200 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())) - ] - fn add_proxy(origin, proxy: T::AccountId, proxy_type: T::ProxyType) -> DispatchResult { + #[weight = T::WeightInfo::add_proxy(T::MaxProxies::get().into())] + fn add_proxy(origin, + delegate: T::AccountId, + proxy_type: T::ProxyType, + delay: T::BlockNumber, + ) -> DispatchResult { let who = ensure_signed(origin)?; Proxies::::try_mutate(&who, |(ref mut proxies, ref mut deposit)| { ensure!(proxies.len() < T::MaxProxies::get() as usize, Error::::TooMany); - let typed_proxy = (proxy, proxy_type); - let i = proxies.binary_search(&typed_proxy).err().ok_or(Error::::Duplicate)?; - proxies.insert(i, typed_proxy); + let proxy_def = ProxyDefinition { delegate, proxy_type, delay }; + let i = proxies.binary_search(&proxy_def).err().ok_or(Error::::Duplicate)?; + proxies.insert(i, proxy_def); let new_deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get() * (proxies.len() as u32).into(); if new_deposit > *deposit { @@ -260,20 +319,19 @@ decl_module! { /// - `proxy_type`: The permissions currently enabled for the removed proxy account. /// /// # - /// P is the number of proxies the user has - /// - Base weight: 14.37 + .164 * P µs - /// - DB weight: 1 storage read and write. + /// Weight is a function of the number of proxies the user has (P). /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(14 * WEIGHT_PER_MICROS) - .saturating_add((160 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())) - ] - fn remove_proxy(origin, proxy: T::AccountId, proxy_type: T::ProxyType) -> DispatchResult { + #[weight = T::WeightInfo::remove_proxy(T::MaxProxies::get().into())] + fn remove_proxy(origin, + delegate: T::AccountId, + proxy_type: T::ProxyType, + delay: T::BlockNumber, + ) -> DispatchResult { let who = ensure_signed(origin)?; Proxies::::try_mutate_exists(&who, |x| { let (mut proxies, old_deposit) = x.take().ok_or(Error::::NotFound)?; - let typed_proxy = (proxy, proxy_type); - let i = proxies.binary_search(&typed_proxy).ok().ok_or(Error::::NotFound)?; + let proxy_def = ProxyDefinition { delegate, proxy_type, delay }; + let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::::NotFound)?; proxies.remove(i); let new_deposit = if proxies.is_empty() { BalanceOf::::zero() @@ -300,14 +358,9 @@ decl_module! { /// the unreserved fees will be inaccessible. **All access to this account will be lost.** /// /// # - /// P is the number of proxies the user has - /// - Base weight: 13.73 + .129 * P µs - /// - DB weight: 1 storage read and write. + /// Weight is a function of the number of proxies the user has (P). /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(14 * WEIGHT_PER_MICROS) - .saturating_add((130 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())) - ] + #[weight = T::WeightInfo::remove_proxies(T::MaxProxies::get().into())] fn remove_proxies(origin) { let who = ensure_signed(origin)?; let (_, old_deposit) = Proxies::::take(&who); @@ -325,6 +378,8 @@ decl_module! { /// - `index`: A disambiguation index, in case this is called multiple times in the same /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just /// want to use `0`. + /// - `delay`: The announcement period required of the initial proxy. Will generally be + /// zero. /// /// Fails with `Duplicate` if this has already been called in this transaction, from the /// same sender, with the same parameters. @@ -332,22 +387,23 @@ decl_module! { /// Fails if there are insufficient funds to pay for deposit. /// /// # - /// P is the number of proxies the user has - /// - Base weight: 36.48 + .039 * P µs - /// - DB weight: 1 storage read and write. + /// Weight is a function of the number of proxies the user has (P). /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(36 * WEIGHT_PER_MICROS) - .saturating_add((40 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())) - ] - fn anonymous(origin, proxy_type: T::ProxyType, index: u16) { + /// TODO: Might be over counting 1 read + #[weight = T::WeightInfo::anonymous(T::MaxProxies::get().into())] + fn anonymous(origin, proxy_type: T::ProxyType, delay: T::BlockNumber, index: u16) { let who = ensure_signed(origin)?; let anonymous = Self::anonymous_account(&who, &proxy_type, index, None); ensure!(!Proxies::::contains_key(&anonymous), Error::::Duplicate); let deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get(); T::Currency::reserve(&who, deposit)?; - Proxies::::insert(&anonymous, (vec![(who.clone(), proxy_type.clone())], deposit)); + let proxy_def = ProxyDefinition { + delegate: who.clone(), + proxy_type: proxy_type.clone(), + delay, + }; + Proxies::::insert(&anonymous, (vec![proxy_def], deposit)); Self::deposit_event(RawEvent::AnonymousCreated(anonymous, who, proxy_type, index)); } @@ -369,14 +425,9 @@ decl_module! { /// account whose `anonymous` call has corresponding parameters. /// /// # - /// P is the number of proxies the user has - /// - Base weight: 15.65 + .137 * P µs - /// - DB weight: 1 storage read and write. + /// Weight is a function of the number of proxies the user has (P). /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(15 * WEIGHT_PER_MICROS) - .saturating_add((140 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())) - ] + #[weight = T::WeightInfo::kill_anonymous(T::MaxProxies::get().into())] fn kill_anonymous(origin, spawner: T::AccountId, proxy_type: T::ProxyType, @@ -393,6 +444,140 @@ decl_module! { let (_, deposit) = Proxies::::take(&who); T::Currency::unreserve(&spawner, deposit); } + + /// Publish the hash of a proxy-call that will be made in the future. + /// + /// This must be called some number of blocks before the corresponding `proxy` is attempted + /// if the delay associated with the proxy relationship is greater than zero. + /// + /// No more than `MaxPending` announcements may be made at any one time. + /// + /// This will take a deposit of `AnnouncementDepositFactor` as well as + /// `AnnouncementDepositBase` if there are no other pending announcements. + /// + /// The dispatch origin for this call must be _Signed_ and a proxy of `real`. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `call_hash`: The hash of the call to be made by the `real` account. + /// + /// # + /// Weight is a function of: + /// - A: the number of announcements made. + /// - P: the number of proxies the user has. + /// # + #[weight = T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get().into())] + fn announce(origin, real: T::AccountId, call_hash: CallHashOf) { + let who = ensure_signed(origin)?; + Proxies::::get(&real).0.into_iter() + .find(|x| &x.delegate == &who) + .ok_or(Error::::NotProxy)?; + + let announcement = Announcement { + real: real.clone(), + call_hash: call_hash.clone(), + height: system::Module::::block_number(), + }; + + Announcements::::try_mutate(&who, |(ref mut pending, ref mut deposit)| { + ensure!(pending.len() < T::MaxPending::get() as usize, Error::::TooMany); + pending.push(announcement); + Self::rejig_deposit( + &who, + *deposit, + T::AnnouncementDepositBase::get(), + T::AnnouncementDepositFactor::get(), + pending.len(), + ).map(|d| d.expect("Just pushed; pending.len() > 0; rejig_deposit returns Some; qed")) + .map(|d| *deposit = d) + })?; + Self::deposit_event(RawEvent::Announced(real, who, call_hash)); + } + + /// Remove a given announcement. + /// + /// May be called by a proxy account to remove a call they previously announced and return + /// the deposit. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `call_hash`: The hash of the call to be made by the `real` account. + /// + /// # + /// Weight is a function of: + /// - A: the number of announcements made. + /// - P: the number of proxies the user has. + /// # + #[weight = T::WeightInfo::remove_announcement(T::MaxPending::get(), T::MaxProxies::get().into())] + fn remove_announcement(origin, real: T::AccountId, call_hash: CallHashOf) { + let who = ensure_signed(origin)?; + Self::edit_announcements(&who, |ann| ann.real != real || ann.call_hash != call_hash)?; + } + + /// Remove the given announcement of a delegate. + /// + /// May be called by a target (proxied) account to remove a call that one of their delegates + /// (`delegate`) has announced they want to execute. The deposit is returned. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `delegate`: The account that previously announced the call. + /// - `call_hash`: The hash of the call to be made. + /// + /// # + /// Weight is a function of: + /// - A: the number of announcements made. + /// - P: the number of proxies the user has. + /// # + #[weight = T::WeightInfo::reject_announcement(T::MaxPending::get(), T::MaxProxies::get().into())] + fn reject_announcement(origin, delegate: T::AccountId, call_hash: CallHashOf) { + let who = ensure_signed(origin)?; + Self::edit_announcements(&delegate, |ann| ann.real != who || ann.call_hash != call_hash)?; + } + + /// Dispatch the given `call` from an account that the sender is authorised for through + /// `add_proxy`. + /// + /// Removes any corresponding announcement(s). + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. + /// - `call`: The call to be made by the `real` account. + /// + /// # + /// Weight is a function of: + /// - A: the number of announcements made. + /// - P: the number of proxies the user has. + /// # + #[weight = { + let di = call.get_dispatch_info(); + (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get().into()) + .saturating_add(di.weight), + di.class) + }] + fn proxy_announced(origin, + delegate: T::AccountId, + real: T::AccountId, + force_proxy_type: Option, + call: Box<::Call>, + ) { + ensure_signed(origin)?; + let def = Self::find_proxy(&real, &delegate, force_proxy_type)?; + + let call_hash = T::CallHasher::hash_of(&call); + let now = system::Module::::block_number(); + Self::edit_announcements(&delegate, |ann| + ann.real != real || ann.call_hash != call_hash || now.saturating_sub(ann.height) < def.delay + ).map_err(|_| Error::::Unannounced)?; + + Self::do_proxy(def, real, *call); + } } } @@ -411,4 +596,82 @@ impl Module { .using_encoded(blake2_256); T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() } + + fn rejig_deposit( + who: &T::AccountId, + old_deposit: BalanceOf, + base: BalanceOf, + factor: BalanceOf, + len: usize, + ) -> Result>, DispatchError> { + let new_deposit = if len == 0 { + BalanceOf::::zero() + } else { + base + factor * (len as u32).into() + }; + if new_deposit > old_deposit { + T::Currency::reserve(&who, new_deposit - old_deposit)?; + } else if new_deposit < old_deposit { + T::Currency::unreserve(&who, old_deposit - new_deposit); + } + Ok(if len == 0 { + None + } else { + Some(new_deposit) + }) + } + + fn edit_announcements< + F: FnMut(&Announcement, T::BlockNumber>) -> bool + >(delegate: &T::AccountId, f: F) -> DispatchResult { + Announcements::::try_mutate_exists(delegate, |x| { + let (mut pending, old_deposit) = x.take().ok_or(Error::::NotFound)?; + let orig_pending_len = pending.len(); + pending.retain(f); + ensure!(orig_pending_len > pending.len(), Error::::NotFound); + *x = Self::rejig_deposit( + delegate, + old_deposit, + T::AnnouncementDepositBase::get(), + T::AnnouncementDepositFactor::get(), + pending.len(), + )?.map(|deposit| (pending, deposit)); + Ok(()) + }) + } + + fn find_proxy( + real: &T::AccountId, + delegate: &T::AccountId, + force_proxy_type: Option, + ) -> Result, DispatchError> { + let f = |x: &ProxyDefinition| -> bool { + &x.delegate == delegate && force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y) + }; + Ok(Proxies::::get(real).0.into_iter().find(f).ok_or(Error::::NotProxy)?) + } + + fn do_proxy( + def: ProxyDefinition, + real: T::AccountId, + call: ::Call, + ) { + // This is a freshly authenticated new account, the origin restrictions doesn't apply. + let mut origin: T::Origin = frame_system::RawOrigin::Signed(real).into(); + origin.add_filter(move |c: &::Call| { + let c = ::Call::from_ref(c); + // We make sure the proxy call does access this pallet to change modify proxies. + match c.is_sub_type() { + // Proxy call cannot add or remove a proxy with more permissions than it already has. + Some(Call::add_proxy(_, ref pt, _)) | Some(Call::remove_proxy(_, ref pt, _)) + if !def.proxy_type.is_superset(&pt) => false, + // Proxy call cannot remove all proxies or kill anonymous proxies unless it has full permissions. + Some(Call::remove_proxies(..)) | Some(Call::kill_anonymous(..)) + if def.proxy_type != T::ProxyType::default() => false, + _ => def.proxy_type.filter(c) + } + }); + let e = call.dispatch(origin); + Self::deposit_event(RawEvent::ProxyExecuted(e.map(|_| ()).map_err(|e| e.error))); + } } diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index 11f11e24d4772..00d84e65ad1d6 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -108,6 +108,9 @@ parameter_types! { pub const ProxyDepositBase: u64 = 1; pub const ProxyDepositFactor: u64 = 1; pub const MaxProxies: u16 = 4; + pub const MaxPending: u32 = 2; + pub const AnnouncementDepositBase: u64 = 1; + pub const AnnouncementDepositFactor: u64 = 1; } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug)] pub enum ProxyType { @@ -148,6 +151,10 @@ impl Trait for Test { type ProxyDepositFactor = ProxyDepositFactor; type MaxProxies = MaxProxies; type WeightInfo = (); + type CallHasher = BlakeTwo256; + type MaxPending = MaxPending; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; } type System = frame_system::Module; @@ -189,13 +196,134 @@ fn expect_events(e: Vec) { assert_eq!(last_events(e.len()), e); } +#[test] +fn announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + assert_eq!(Balances::reserved_balance(3), 0); + + assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); + assert_eq!(Announcements::::get(3), (vec![Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1, + }], 2)); + assert_eq!(Balances::reserved_balance(3), 2); + + assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + assert_eq!(Announcements::::get(3), (vec![ + Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1, + }, + Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1, + }, + ], 3)); + assert_eq!(Balances::reserved_balance(3), 3); + + assert_noop!(Proxy::announce(Origin::signed(3), 2, [3; 32].into()), Error::::TooMany); + }); +} + +#[test] +fn remove_announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + let e = Error::::NotFound; + assert_noop!(Proxy::remove_announcement(Origin::signed(3), 1, [0; 32].into()), e); + assert_ok!(Proxy::remove_announcement(Origin::signed(3), 1, [1; 32].into())); + assert_eq!(Announcements::::get(3), (vec![Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1, + }], 2)); + assert_eq!(Balances::reserved_balance(3), 2); + }); +} + +#[test] +fn reject_announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); + let e = Error::::NotFound; + assert_noop!(Proxy::reject_announcement(Origin::signed(1), 3, [0; 32].into()), e); + let e = Error::::NotFound; + assert_noop!(Proxy::reject_announcement(Origin::signed(4), 3, [1; 32].into()), e); + assert_ok!(Proxy::reject_announcement(Origin::signed(1), 3, [1; 32].into())); + assert_eq!(Announcements::::get(3), (vec![Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1, + }], 2)); + assert_eq!(Balances::reserved_balance(3), 2); + }); +} + +#[test] +fn announcer_must_be_proxy() { + new_test_ext().execute_with(|| { + assert_noop!(Proxy::announce(Origin::signed(2), 1, H256::zero()), Error::::NotProxy); + }); +} + +#[test] +fn delayed_requires_pre_announcement() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 1)); + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); + let e = Error::::Unannounced; + assert_noop!(Proxy::proxy(Origin::signed(2), 1, None, call.clone()), e); + let e = Error::::Unannounced; + assert_noop!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone()), e); + let call_hash = BlakeTwo256::hash_of(&call); + assert_ok!(Proxy::announce(Origin::signed(2), 1, call_hash)); + system::Module::::set_block_number(2); + assert_ok!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone())); + }); +} + +#[test] +fn proxy_announced_removes_announcement_and_returns_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); + assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); + let call_hash = BlakeTwo256::hash_of(&call); + assert_ok!(Proxy::announce(Origin::signed(3), 1, call_hash)); + assert_ok!(Proxy::announce(Origin::signed(3), 2, call_hash)); + // Too early to execute announced call + let e = Error::::Unannounced; + assert_noop!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone()), e); + + system::Module::::set_block_number(2); + assert_ok!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone())); + assert_eq!(Announcements::::get(3), (vec![Announcement { + real: 2, + call_hash, + height: 1, + }], 2)); + assert_eq!(Balances::reserved_balance(3), 2); + }); +} + #[test] fn filtering_works() { new_test_ext().execute_with(|| { Balances::mutate_account(&1, |a| a.free = 1000); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::JustTransfer)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); @@ -228,7 +356,7 @@ fn filtering_works() { RawEvent::ProxyExecuted(Ok(())).into(), ]); - let inner = Box::new(Call::Proxy(ProxyCall::add_proxy(5, ProxyType::Any))); + let inner = Box::new(Call::Proxy(ProxyCall::add_proxy(5, ProxyType::Any, 0))); let call = Box::new(Call::Utility(UtilityCall::batch(vec![*inner]))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_events(vec![UtilityEvent::BatchCompleted.into(), RawEvent::ProxyExecuted(Ok(())).into()]); @@ -253,24 +381,24 @@ fn filtering_works() { #[test] fn add_remove_proxies_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any)); - assert_noop!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any), Error::::Duplicate); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); + assert_noop!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0), Error::::Duplicate); assert_eq!(Balances::reserved_balance(1), 2); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 3); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); assert_eq!(Balances::reserved_balance(1), 5); - assert_noop!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::Any), Error::::TooMany); - assert_noop!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::JustTransfer), Error::::NotFound); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 4, ProxyType::JustUtility)); + assert_noop!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::Any, 0), Error::::TooMany); + assert_noop!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0), Error::::NotFound); + assert_ok!(Proxy::remove_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any)); + assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 3); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any)); + assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 2); - assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer)); + assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -278,10 +406,10 @@ fn add_remove_proxies_works() { #[test] fn cannot_add_proxy_without_balance() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(5), 3, ProxyType::Any)); + assert_ok!(Proxy::add_proxy(Origin::signed(5), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(5), 2); assert_noop!( - Proxy::add_proxy(Origin::signed(5), 4, ProxyType::Any), + Proxy::add_proxy(Origin::signed(5), 4, ProxyType::Any, 0), BalancesError::::InsufficientBalance ); }); @@ -290,8 +418,8 @@ fn cannot_add_proxy_without_balance() { #[test] fn proxying_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer)); - assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); + assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_noop!(Proxy::proxy(Origin::signed(4), 1, None, call.clone()), Error::::NotProxy); @@ -319,21 +447,21 @@ fn proxying_works() { #[test] fn anonymous_works() { new_test_ext().execute_with(|| { - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0)); + assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); let anon = Proxy::anonymous_account(&1, &ProxyType::Any, 0, None); expect_event(RawEvent::AnonymousCreated(anon.clone(), 1, ProxyType::Any, 0)); // other calls to anonymous allowed as long as they're not exactly the same. - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::JustTransfer, 0)); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 1)); + assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::JustTransfer, 0, 0)); + assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 1)); let anon2 = Proxy::anonymous_account(&2, &ProxyType::Any, 0, None); - assert_ok!(Proxy::anonymous(Origin::signed(2), ProxyType::Any, 0)); - assert_noop!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0), Error::::Duplicate); + assert_ok!(Proxy::anonymous(Origin::signed(2), ProxyType::Any, 0, 0)); + assert_noop!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0), Error::::Duplicate); System::set_extrinsic_index(1); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0)); + assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); System::set_extrinsic_index(0); System::set_block_number(2); - assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0)); + assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_ok!(Balances::transfer(Origin::signed(3), anon, 5));