Skip to main content

pezpallet_proxy/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Proxy Pezpallet
19//! A pezpallet allowing accounts to give permission to other accounts to dispatch types of calls
20//! from their signed origin.
21//!
22//! The accounts to which permission is delegated may be required to announce the action that they
23//! wish to execute some duration prior to execution happens. In this case, the target account may
24//! reject the announcement and in doing so, veto the execution.
25//!
26//! - [`Config`]
27//! - [`Call`]
28
29// Ensure we're `no_std` when compiling for Wasm.
30#![cfg_attr(not(feature = "std"), no_std)]
31
32mod benchmarking;
33mod tests;
34pub mod weights;
35
36extern crate alloc;
37use alloc::{boxed::Box, vec};
38use frame::{
39	prelude::*,
40	traits::{Currency, InstanceFilter, ReservableCurrency},
41};
42pub use pezpallet::*;
43pub use weights::WeightInfo;
44
45type CallHashOf<T> = <<T as Config>::CallHasher as Hash>::Output;
46
47type BalanceOf<T> =
48	<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
49
50pub type BlockNumberFor<T> =
51	<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
52
53type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
54
55/// The parameters under which a particular account has a proxy relationship with some other
56/// account.
57#[derive(
58	Encode,
59	Decode,
60	DecodeWithMemTracking,
61	Clone,
62	Copy,
63	Eq,
64	PartialEq,
65	Ord,
66	PartialOrd,
67	RuntimeDebug,
68	MaxEncodedLen,
69	TypeInfo,
70)]
71pub struct ProxyDefinition<AccountId, ProxyType, BlockNumber> {
72	/// The account which may act on behalf of another.
73	pub delegate: AccountId,
74	/// A value defining the subset of calls that it is allowed to make.
75	pub proxy_type: ProxyType,
76	/// The number of blocks that an announcement must be in place for before the corresponding
77	/// call may be dispatched. If zero, then no announcement is needed.
78	pub delay: BlockNumber,
79}
80
81/// Details surrounding a specific instance of an announcement to make a call.
82#[derive(
83	Encode,
84	Decode,
85	DecodeWithMemTracking,
86	Clone,
87	Copy,
88	Eq,
89	PartialEq,
90	RuntimeDebug,
91	MaxEncodedLen,
92	TypeInfo,
93)]
94pub struct Announcement<AccountId, Hash, BlockNumber> {
95	/// The account which made the announcement.
96	real: AccountId,
97	/// The hash of the call to be made.
98	call_hash: Hash,
99	/// The height at which the announcement was made.
100	height: BlockNumber,
101}
102
103/// The type of deposit
104#[derive(
105	Encode,
106	Decode,
107	Clone,
108	Copy,
109	Eq,
110	PartialEq,
111	RuntimeDebug,
112	MaxEncodedLen,
113	TypeInfo,
114	DecodeWithMemTracking,
115)]
116pub enum DepositKind {
117	/// Proxy registration deposit
118	Proxies,
119	/// Announcement deposit
120	Announcements,
121}
122
123#[frame::pezpallet]
124pub mod pezpallet {
125	use super::*;
126
127	#[pezpallet::pezpallet]
128	pub struct Pezpallet<T>(_);
129
130	/// Configuration trait.
131	#[pezpallet::config]
132	pub trait Config: pezframe_system::Config {
133		/// The overarching event type.
134		#[allow(deprecated)]
135		type RuntimeEvent: From<Event<Self>>
136			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
137
138		/// The overarching call type.
139		type RuntimeCall: Parameter
140			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
141			+ GetDispatchInfo
142			+ From<pezframe_system::Call<Self>>
143			+ IsSubType<Call<Self>>
144			+ IsType<<Self as pezframe_system::Config>::RuntimeCall>;
145
146		/// The currency mechanism.
147		type Currency: ReservableCurrency<Self::AccountId>;
148
149		/// A kind of proxy; specified with the proxy and passed in to the `IsProxyable` filter.
150		/// The instance filter determines whether a given call may be proxied under this type.
151		///
152		/// IMPORTANT: `Default` must be provided and MUST BE the the *most permissive* value.
153		type ProxyType: Parameter
154			+ Member
155			+ Ord
156			+ PartialOrd
157			+ frame::traits::InstanceFilter<<Self as Config>::RuntimeCall>
158			+ Default
159			+ MaxEncodedLen;
160
161		/// The base amount of currency needed to reserve for creating a proxy.
162		///
163		/// This is held for an additional storage item whose value size is
164		/// `sizeof(Balance)` bytes and whose key size is `sizeof(AccountId)` bytes.
165		#[pezpallet::constant]
166		type ProxyDepositBase: Get<BalanceOf<Self>>;
167
168		/// The amount of currency needed per proxy added.
169		///
170		/// This is held for adding 32 bytes plus an instance of `ProxyType` more into a
171		/// pre-existing storage value. Thus, when configuring `ProxyDepositFactor` one should take
172		/// into account `32 + proxy_type.encode().len()` bytes of data.
173		#[pezpallet::constant]
174		type ProxyDepositFactor: Get<BalanceOf<Self>>;
175
176		/// The maximum amount of proxies allowed for a single account.
177		#[pezpallet::constant]
178		type MaxProxies: Get<u32>;
179
180		/// Weight information for extrinsics in this pezpallet.
181		type WeightInfo: WeightInfo;
182
183		/// The maximum amount of time-delayed announcements that are allowed to be pending.
184		#[pezpallet::constant]
185		type MaxPending: Get<u32>;
186
187		/// The type of hash used for hashing the call.
188		type CallHasher: Hash;
189
190		/// The base amount of currency needed to reserve for creating an announcement.
191		///
192		/// This is held when a new storage item holding a `Balance` is created (typically 16
193		/// bytes).
194		#[pezpallet::constant]
195		type AnnouncementDepositBase: Get<BalanceOf<Self>>;
196
197		/// The amount of currency needed per announcement made.
198		///
199		/// This is held for adding an `AccountId`, `Hash` and `BlockNumber` (typically 68 bytes)
200		/// into a pre-existing storage value.
201		#[pezpallet::constant]
202		type AnnouncementDepositFactor: Get<BalanceOf<Self>>;
203
204		/// Query the current block number.
205		///
206		/// Must return monotonically increasing values when called from consecutive blocks.
207		/// Can be configured to return either:
208		/// - the local block number of the runtime via `pezframe_system::Pezpallet`
209		/// - a remote block number, eg from the relay chain through `RelaychainDataProvider`
210		/// - an arbitrary value through a custom implementation of the trait
211		///
212		/// There is currently no migration provided to "hot-swap" block number providers and it may
213		/// result in undefined behavior when doing so. Teyrchains are therefore best off setting
214		/// this to their local block number provider if they have the pezpallet already deployed.
215		///
216		/// Suggested values:
217		/// - Solo- and Relay-chains: `pezframe_system::Pezpallet`
218		/// - Teyrchains that may produce blocks sparingly or only when needed (on-demand):
219		///   - already have the pezpallet deployed: `pezframe_system::Pezpallet`
220		///   - are freshly deploying this pezpallet: `RelaychainDataProvider`
221		/// - Teyrchains with a reliably block production rate (PLO or bulk-coretime):
222		///   - already have the pezpallet deployed: `pezframe_system::Pezpallet`
223		///   - are freshly deploying this pezpallet: no strong recommendation. Both local and
224		///     remote providers can be used. Relay provider can be a bit better in cases where the
225		///     teyrchain is lagging its block production to avoid clock skew.
226		type BlockNumberProvider: BlockNumberProvider;
227	}
228
229	#[pezpallet::call]
230	impl<T: Config> Pezpallet<T> {
231		/// Dispatch the given `call` from an account that the sender is authorised for through
232		/// `add_proxy`.
233		///
234		/// The dispatch origin for this call must be _Signed_.
235		///
236		/// Parameters:
237		/// - `real`: The account that the proxy will make a call on behalf of.
238		/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
239		/// - `call`: The call to be made by the `real` account.
240		#[pezpallet::call_index(0)]
241		#[pezpallet::weight({
242			let di = call.get_dispatch_info();
243			(T::WeightInfo::proxy(T::MaxProxies::get())
244				 // AccountData for inner call origin accountdata.
245				.saturating_add(T::DbWeight::get().reads_writes(1, 1))
246				.saturating_add(di.call_weight),
247			di.class)
248		})]
249		pub fn proxy(
250			origin: OriginFor<T>,
251			real: AccountIdLookupOf<T>,
252			force_proxy_type: Option<T::ProxyType>,
253			call: Box<<T as Config>::RuntimeCall>,
254		) -> DispatchResult {
255			let who = ensure_signed(origin)?;
256			let real = T::Lookup::lookup(real)?;
257			let def = Self::find_proxy(&real, &who, force_proxy_type)?;
258			ensure!(def.delay.is_zero(), Error::<T>::Unannounced);
259
260			Self::do_proxy(def, real, *call);
261
262			Ok(())
263		}
264
265		/// Register a proxy account for the sender that is able to make calls on its behalf.
266		///
267		/// The dispatch origin for this call must be _Signed_.
268		///
269		/// Parameters:
270		/// - `proxy`: The account that the `caller` would like to make a proxy.
271		/// - `proxy_type`: The permissions allowed for this proxy account.
272		/// - `delay`: The announcement period required of the initial proxy. Will generally be
273		/// zero.
274		#[pezpallet::call_index(1)]
275		#[pezpallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))]
276		pub fn add_proxy(
277			origin: OriginFor<T>,
278			delegate: AccountIdLookupOf<T>,
279			proxy_type: T::ProxyType,
280			delay: BlockNumberFor<T>,
281		) -> DispatchResult {
282			let who = ensure_signed(origin)?;
283			let delegate = T::Lookup::lookup(delegate)?;
284			Self::add_proxy_delegate(&who, delegate, proxy_type, delay)
285		}
286
287		/// Unregister a proxy account for the sender.
288		///
289		/// The dispatch origin for this call must be _Signed_.
290		///
291		/// Parameters:
292		/// - `proxy`: The account that the `caller` would like to remove as a proxy.
293		/// - `proxy_type`: The permissions currently enabled for the removed proxy account.
294		#[pezpallet::call_index(2)]
295		#[pezpallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))]
296		pub fn remove_proxy(
297			origin: OriginFor<T>,
298			delegate: AccountIdLookupOf<T>,
299			proxy_type: T::ProxyType,
300			delay: BlockNumberFor<T>,
301		) -> DispatchResult {
302			let who = ensure_signed(origin)?;
303			let delegate = T::Lookup::lookup(delegate)?;
304			Self::remove_proxy_delegate(&who, delegate, proxy_type, delay)
305		}
306
307		/// Unregister all proxy accounts for the sender.
308		///
309		/// The dispatch origin for this call must be _Signed_.
310		///
311		/// WARNING: This may be called on accounts created by `create_pure`, however if done, then
312		/// the unreserved fees will be inaccessible. **All access to this account will be lost.**
313		#[pezpallet::call_index(3)]
314		#[pezpallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))]
315		pub fn remove_proxies(origin: OriginFor<T>) -> DispatchResult {
316			let who = ensure_signed(origin)?;
317			Self::remove_all_proxy_delegates(&who);
318			Ok(())
319		}
320
321		/// Spawn a fresh new account that is guaranteed to be otherwise inaccessible, and
322		/// initialize it with a proxy of `proxy_type` for `origin` sender.
323		///
324		/// Requires a `Signed` origin.
325		///
326		/// - `proxy_type`: The type of the proxy that the sender will be registered as over the
327		/// new account. This will almost always be the most permissive `ProxyType` possible to
328		/// allow for maximum flexibility.
329		/// - `index`: A disambiguation index, in case this is called multiple times in the same
330		/// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just
331		/// want to use `0`.
332		/// - `delay`: The announcement period required of the initial proxy. Will generally be
333		/// zero.
334		///
335		/// Fails with `Duplicate` if this has already been called in this transaction, from the
336		/// same sender, with the same parameters.
337		///
338		/// Fails if there are insufficient funds to pay for deposit.
339		#[pezpallet::call_index(4)]
340		#[pezpallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))]
341		pub fn create_pure(
342			origin: OriginFor<T>,
343			proxy_type: T::ProxyType,
344			delay: BlockNumberFor<T>,
345			index: u16,
346		) -> DispatchResult {
347			let who = ensure_signed(origin)?;
348
349			let pure = Self::pure_account(&who, &proxy_type, index, None);
350			ensure!(!Proxies::<T>::contains_key(&pure), Error::<T>::Duplicate);
351
352			let proxy_def =
353				ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay };
354			let bounded_proxies: BoundedVec<_, T::MaxProxies> =
355				vec![proxy_def].try_into().map_err(|_| Error::<T>::TooMany)?;
356
357			let deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get();
358			T::Currency::reserve(&who, deposit)?;
359
360			Proxies::<T>::insert(&pure, (bounded_proxies, deposit));
361			let extrinsic_index =
362				<pezframe_system::Pezpallet<T>>::extrinsic_index().unwrap_or_default();
363			Self::deposit_event(Event::PureCreated {
364				pure,
365				who,
366				proxy_type,
367				disambiguation_index: index,
368				at: T::BlockNumberProvider::current_block_number(),
369				extrinsic_index,
370			});
371
372			Ok(())
373		}
374
375		/// Removes a previously spawned pure proxy.
376		///
377		/// WARNING: **All access to this account will be lost.** Any funds held in it will be
378		/// inaccessible.
379		///
380		/// Requires a `Signed` origin, and the sender account must have been created by a call to
381		/// `create_pure` with corresponding parameters.
382		///
383		/// - `spawner`: The account that originally called `create_pure` to create this account.
384		/// - `index`: The disambiguation index originally passed to `create_pure`. Probably `0`.
385		/// - `proxy_type`: The proxy type originally passed to `create_pure`.
386		/// - `height`: The height of the chain when the call to `create_pure` was processed.
387		/// - `ext_index`: The extrinsic index in which the call to `create_pure` was processed.
388		///
389		/// Fails with `NoPermission` in case the caller is not a previously created pure
390		/// account whose `create_pure` call has corresponding parameters.
391		#[pezpallet::call_index(5)]
392		#[pezpallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))]
393		pub fn kill_pure(
394			origin: OriginFor<T>,
395			spawner: AccountIdLookupOf<T>,
396			proxy_type: T::ProxyType,
397			index: u16,
398			#[pezpallet::compact] height: BlockNumberFor<T>,
399			#[pezpallet::compact] ext_index: u32,
400		) -> DispatchResult {
401			let who = ensure_signed(origin)?;
402			let spawner = T::Lookup::lookup(spawner)?;
403
404			let when = (height, ext_index);
405			let proxy = Self::pure_account(&spawner, &proxy_type, index, Some(when));
406			ensure!(proxy == who, Error::<T>::NoPermission);
407
408			let (_, deposit) = Proxies::<T>::take(&who);
409			T::Currency::unreserve(&spawner, deposit);
410
411			Self::deposit_event(Event::PureKilled {
412				pure: who,
413				spawner,
414				proxy_type,
415				disambiguation_index: index,
416			});
417
418			Ok(())
419		}
420
421		/// Publish the hash of a proxy-call that will be made in the future.
422		///
423		/// This must be called some number of blocks before the corresponding `proxy` is attempted
424		/// if the delay associated with the proxy relationship is greater than zero.
425		///
426		/// No more than `MaxPending` announcements may be made at any one time.
427		///
428		/// This will take a deposit of `AnnouncementDepositFactor` as well as
429		/// `AnnouncementDepositBase` if there are no other pending announcements.
430		///
431		/// The dispatch origin for this call must be _Signed_ and a proxy of `real`.
432		///
433		/// Parameters:
434		/// - `real`: The account that the proxy will make a call on behalf of.
435		/// - `call_hash`: The hash of the call to be made by the `real` account.
436		#[pezpallet::call_index(6)]
437		#[pezpallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))]
438		pub fn announce(
439			origin: OriginFor<T>,
440			real: AccountIdLookupOf<T>,
441			call_hash: CallHashOf<T>,
442		) -> DispatchResult {
443			let who = ensure_signed(origin)?;
444			let real = T::Lookup::lookup(real)?;
445			Proxies::<T>::get(&real)
446				.0
447				.into_iter()
448				.find(|x| x.delegate == who)
449				.ok_or(Error::<T>::NotProxy)?;
450
451			let announcement = Announcement {
452				real: real.clone(),
453				call_hash,
454				height: T::BlockNumberProvider::current_block_number(),
455			};
456
457			Announcements::<T>::try_mutate(&who, |(ref mut pending, ref mut deposit)| {
458				pending.try_push(announcement).map_err(|_| Error::<T>::TooMany)?;
459				Self::rejig_deposit(
460					&who,
461					*deposit,
462					T::AnnouncementDepositBase::get(),
463					T::AnnouncementDepositFactor::get(),
464					pending.len(),
465				)
466				.map(|d| {
467					d.expect("Just pushed; pending.len() > 0; rejig_deposit returns Some; qed")
468				})
469				.map(|d| *deposit = d)
470			})?;
471			Self::deposit_event(Event::Announced { real, proxy: who, call_hash });
472
473			Ok(())
474		}
475
476		/// Remove a given announcement.
477		///
478		/// May be called by a proxy account to remove a call they previously announced and return
479		/// the deposit.
480		///
481		/// The dispatch origin for this call must be _Signed_.
482		///
483		/// Parameters:
484		/// - `real`: The account that the proxy will make a call on behalf of.
485		/// - `call_hash`: The hash of the call to be made by the `real` account.
486		#[pezpallet::call_index(7)]
487		#[pezpallet::weight(T::WeightInfo::remove_announcement(
488			T::MaxPending::get(),
489			T::MaxProxies::get()
490		))]
491		pub fn remove_announcement(
492			origin: OriginFor<T>,
493			real: AccountIdLookupOf<T>,
494			call_hash: CallHashOf<T>,
495		) -> DispatchResult {
496			let who = ensure_signed(origin)?;
497			let real = T::Lookup::lookup(real)?;
498			Self::edit_announcements(&who, |ann| ann.real != real || ann.call_hash != call_hash)?;
499
500			Ok(())
501		}
502
503		/// Remove the given announcement of a delegate.
504		///
505		/// May be called by a target (proxied) account to remove a call that one of their delegates
506		/// (`delegate`) has announced they want to execute. The deposit is returned.
507		///
508		/// The dispatch origin for this call must be _Signed_.
509		///
510		/// Parameters:
511		/// - `delegate`: The account that previously announced the call.
512		/// - `call_hash`: The hash of the call to be made.
513		#[pezpallet::call_index(8)]
514		#[pezpallet::weight(T::WeightInfo::reject_announcement(
515			T::MaxPending::get(),
516			T::MaxProxies::get()
517		))]
518		pub fn reject_announcement(
519			origin: OriginFor<T>,
520			delegate: AccountIdLookupOf<T>,
521			call_hash: CallHashOf<T>,
522		) -> DispatchResult {
523			let who = ensure_signed(origin)?;
524			let delegate = T::Lookup::lookup(delegate)?;
525			Self::edit_announcements(&delegate, |ann| {
526				ann.real != who || ann.call_hash != call_hash
527			})?;
528
529			Ok(())
530		}
531
532		/// Dispatch the given `call` from an account that the sender is authorized for through
533		/// `add_proxy`.
534		///
535		/// Removes any corresponding announcement(s).
536		///
537		/// The dispatch origin for this call must be _Signed_.
538		///
539		/// Parameters:
540		/// - `real`: The account that the proxy will make a call on behalf of.
541		/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
542		/// - `call`: The call to be made by the `real` account.
543		#[pezpallet::call_index(9)]
544		#[pezpallet::weight({
545			let di = call.get_dispatch_info();
546			(T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get())
547				 // AccountData for inner call origin accountdata.
548				.saturating_add(T::DbWeight::get().reads_writes(1, 1))
549				.saturating_add(di.call_weight),
550			di.class)
551		})]
552		pub fn proxy_announced(
553			origin: OriginFor<T>,
554			delegate: AccountIdLookupOf<T>,
555			real: AccountIdLookupOf<T>,
556			force_proxy_type: Option<T::ProxyType>,
557			call: Box<<T as Config>::RuntimeCall>,
558		) -> DispatchResult {
559			ensure_signed(origin)?;
560			let delegate = T::Lookup::lookup(delegate)?;
561			let real = T::Lookup::lookup(real)?;
562			let def = Self::find_proxy(&real, &delegate, force_proxy_type)?;
563
564			let call_hash = T::CallHasher::hash_of(&call);
565			let now = T::BlockNumberProvider::current_block_number();
566			Self::edit_announcements(&delegate, |ann| {
567				ann.real != real
568					|| ann.call_hash != call_hash
569					|| now.saturating_sub(ann.height) < def.delay
570			})
571			.map_err(|_| Error::<T>::Unannounced)?;
572
573			Self::do_proxy(def, real, *call);
574
575			Ok(())
576		}
577
578		/// Poke / Adjust deposits made for proxies and announcements based on current values.
579		/// This can be used by accounts to possibly lower their locked amount.
580		///
581		/// The dispatch origin for this call must be _Signed_.
582		///
583		/// The transaction fee is waived if the deposit amount has changed.
584		///
585		/// Emits `DepositPoked` if successful.
586		#[pezpallet::call_index(10)]
587		#[pezpallet::weight(T::WeightInfo::poke_deposit())]
588		pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
589			let who = ensure_signed(origin)?;
590			let mut deposit_updated = false;
591
592			// Check and update proxy deposits
593			Proxies::<T>::try_mutate_exists(&who, |maybe_proxies| -> DispatchResult {
594				let (proxies, old_deposit) = maybe_proxies.take().unwrap_or_default();
595				let maybe_new_deposit = Self::rejig_deposit(
596					&who,
597					old_deposit,
598					T::ProxyDepositBase::get(),
599					T::ProxyDepositFactor::get(),
600					proxies.len(),
601				)?;
602
603				match maybe_new_deposit {
604					Some(new_deposit) if new_deposit != old_deposit => {
605						*maybe_proxies = Some((proxies, new_deposit));
606						deposit_updated = true;
607						Self::deposit_event(Event::DepositPoked {
608							who: who.clone(),
609							kind: DepositKind::Proxies,
610							old_deposit,
611							new_deposit,
612						});
613					},
614					Some(_) => {
615						*maybe_proxies = Some((proxies, old_deposit));
616					},
617					None => {
618						*maybe_proxies = None;
619						if !old_deposit.is_zero() {
620							deposit_updated = true;
621							Self::deposit_event(Event::DepositPoked {
622								who: who.clone(),
623								kind: DepositKind::Proxies,
624								old_deposit,
625								new_deposit: BalanceOf::<T>::zero(),
626							});
627						}
628					},
629				}
630				Ok(())
631			})?;
632
633			// Check and update announcement deposits
634			Announcements::<T>::try_mutate_exists(&who, |maybe_announcements| -> DispatchResult {
635				let (announcements, old_deposit) = maybe_announcements.take().unwrap_or_default();
636				let maybe_new_deposit = Self::rejig_deposit(
637					&who,
638					old_deposit,
639					T::AnnouncementDepositBase::get(),
640					T::AnnouncementDepositFactor::get(),
641					announcements.len(),
642				)?;
643
644				match maybe_new_deposit {
645					Some(new_deposit) if new_deposit != old_deposit => {
646						*maybe_announcements = Some((announcements, new_deposit));
647						deposit_updated = true;
648						Self::deposit_event(Event::DepositPoked {
649							who: who.clone(),
650							kind: DepositKind::Announcements,
651							old_deposit,
652							new_deposit,
653						});
654					},
655					Some(_) => {
656						*maybe_announcements = Some((announcements, old_deposit));
657					},
658					None => {
659						*maybe_announcements = None;
660						if !old_deposit.is_zero() {
661							deposit_updated = true;
662							Self::deposit_event(Event::DepositPoked {
663								who: who.clone(),
664								kind: DepositKind::Announcements,
665								old_deposit,
666								new_deposit: BalanceOf::<T>::zero(),
667							});
668						}
669					},
670				}
671				Ok(())
672			})?;
673
674			Ok(if deposit_updated { Pays::No.into() } else { Pays::Yes.into() })
675		}
676	}
677
678	#[pezpallet::event]
679	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
680	pub enum Event<T: Config> {
681		/// A proxy was executed correctly, with the given.
682		ProxyExecuted { result: DispatchResult },
683		/// A pure account has been created by new proxy with given
684		/// disambiguation index and proxy type.
685		PureCreated {
686			pure: T::AccountId,
687			who: T::AccountId,
688			proxy_type: T::ProxyType,
689			disambiguation_index: u16,
690			at: BlockNumberFor<T>,
691			extrinsic_index: u32,
692		},
693		/// A pure proxy was killed by its spawner.
694		PureKilled {
695			// The pure proxy account that was destroyed.
696			pure: T::AccountId,
697			// The account that created the pure proxy.
698			spawner: T::AccountId,
699			// The proxy type of the pure proxy that was destroyed.
700			proxy_type: T::ProxyType,
701			// The index originally passed to `create_pure` when this pure proxy was created.
702			disambiguation_index: u16,
703		},
704		/// An announcement was placed to make a call in the future.
705		Announced { real: T::AccountId, proxy: T::AccountId, call_hash: CallHashOf<T> },
706		/// A proxy was added.
707		ProxyAdded {
708			delegator: T::AccountId,
709			delegatee: T::AccountId,
710			proxy_type: T::ProxyType,
711			delay: BlockNumberFor<T>,
712		},
713		/// A proxy was removed.
714		ProxyRemoved {
715			delegator: T::AccountId,
716			delegatee: T::AccountId,
717			proxy_type: T::ProxyType,
718			delay: BlockNumberFor<T>,
719		},
720		/// A deposit stored for proxies or announcements was poked / updated.
721		DepositPoked {
722			who: T::AccountId,
723			kind: DepositKind,
724			old_deposit: BalanceOf<T>,
725			new_deposit: BalanceOf<T>,
726		},
727	}
728
729	#[pezpallet::error]
730	pub enum Error<T> {
731		/// There are too many proxies registered or too many announcements pending.
732		TooMany,
733		/// Proxy registration not found.
734		NotFound,
735		/// Sender is not a proxy of the account to be proxied.
736		NotProxy,
737		/// A call which is incompatible with the proxy type's filter was attempted.
738		Unproxyable,
739		/// Account is already a proxy.
740		Duplicate,
741		/// Call may not be made by proxy because it may escalate its privileges.
742		NoPermission,
743		/// Announcement, if made at all, was made too recently.
744		Unannounced,
745		/// Cannot add self as proxy.
746		NoSelfProxy,
747	}
748
749	/// The set of account proxies. Maps the account which has delegated to the accounts
750	/// which are being delegated to, together with the amount held on deposit.
751	#[pezpallet::storage]
752	pub type Proxies<T: Config> = StorageMap<
753		_,
754		Twox64Concat,
755		T::AccountId,
756		(
757			BoundedVec<
758				ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>,
759				T::MaxProxies,
760			>,
761			BalanceOf<T>,
762		),
763		ValueQuery,
764	>;
765
766	/// The announcements made by the proxy (key).
767	#[pezpallet::storage]
768	pub type Announcements<T: Config> = StorageMap<
769		_,
770		Twox64Concat,
771		T::AccountId,
772		(
773			BoundedVec<Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>, T::MaxPending>,
774			BalanceOf<T>,
775		),
776		ValueQuery,
777	>;
778
779	#[pezpallet::view_functions]
780	impl<T: Config> Pezpallet<T> {
781		/// Check if a `RuntimeCall` is allowed for a given `ProxyType`.
782		pub fn check_permissions(
783			call: <T as Config>::RuntimeCall,
784			proxy_type: T::ProxyType,
785		) -> bool {
786			proxy_type.filter(&call)
787		}
788
789		/// Check if one `ProxyType` is a subset of another `ProxyType`.
790		pub fn is_superset(to_check: T::ProxyType, against: T::ProxyType) -> bool {
791			to_check.is_superset(&against)
792		}
793	}
794}
795
796impl<T: Config> Pezpallet<T> {
797	/// Public function to proxies storage.
798	pub fn proxies(
799		account: T::AccountId,
800	) -> (
801		BoundedVec<ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>, T::MaxProxies>,
802		BalanceOf<T>,
803	) {
804		Proxies::<T>::get(account)
805	}
806
807	/// Public function to announcements storage.
808	pub fn announcements(
809		account: T::AccountId,
810	) -> (
811		BoundedVec<Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>, T::MaxPending>,
812		BalanceOf<T>,
813	) {
814		Announcements::<T>::get(account)
815	}
816
817	/// Calculate the address of an pure account.
818	///
819	/// - `who`: The spawner account.
820	/// - `proxy_type`: The type of the proxy that the sender will be registered as over the
821	/// new account. This will almost always be the most permissive `ProxyType` possible to
822	/// allow for maximum flexibility.
823	/// - `index`: A disambiguation index, in case this is called multiple times in the same
824	/// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just
825	/// want to use `0`.
826	/// - `maybe_when`: The block height and extrinsic index of when the pure account was
827	/// created. None to use current block height and extrinsic index.
828	pub fn pure_account(
829		who: &T::AccountId,
830		proxy_type: &T::ProxyType,
831		index: u16,
832		maybe_when: Option<(BlockNumberFor<T>, u32)>,
833	) -> T::AccountId {
834		let (height, ext_index) = maybe_when.unwrap_or_else(|| {
835			(
836				T::BlockNumberProvider::current_block_number(),
837				pezframe_system::Pezpallet::<T>::extrinsic_index().unwrap_or_default(),
838			)
839		});
840
841		let entropy = (b"modlpy/proxy____", who, height, ext_index, proxy_type, index)
842			.using_encoded(blake2_256);
843		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
844			.expect("infinite length input; no invalid inputs for type; qed")
845	}
846
847	/// Register a proxy account for the delegator that is able to make calls on its behalf.
848	///
849	/// Parameters:
850	/// - `delegator`: The delegator account.
851	/// - `delegatee`: The account that the `delegator` would like to make a proxy.
852	/// - `proxy_type`: The permissions allowed for this proxy account.
853	/// - `delay`: The announcement period required of the initial proxy. Will generally be
854	/// zero.
855	pub fn add_proxy_delegate(
856		delegator: &T::AccountId,
857		delegatee: T::AccountId,
858		proxy_type: T::ProxyType,
859		delay: BlockNumberFor<T>,
860	) -> DispatchResult {
861		ensure!(delegator != &delegatee, Error::<T>::NoSelfProxy);
862		Proxies::<T>::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| {
863			let proxy_def = ProxyDefinition {
864				delegate: delegatee.clone(),
865				proxy_type: proxy_type.clone(),
866				delay,
867			};
868			let i = proxies.binary_search(&proxy_def).err().ok_or(Error::<T>::Duplicate)?;
869			proxies.try_insert(i, proxy_def).map_err(|_| Error::<T>::TooMany)?;
870			let new_deposit = Self::deposit(proxies.len() as u32);
871			if new_deposit > *deposit {
872				T::Currency::reserve(delegator, new_deposit - *deposit)?;
873			} else if new_deposit < *deposit {
874				T::Currency::unreserve(delegator, *deposit - new_deposit);
875			}
876			*deposit = new_deposit;
877			Self::deposit_event(Event::<T>::ProxyAdded {
878				delegator: delegator.clone(),
879				delegatee,
880				proxy_type,
881				delay,
882			});
883			Ok(())
884		})
885	}
886
887	/// Unregister a proxy account for the delegator.
888	///
889	/// Parameters:
890	/// - `delegator`: The delegator account.
891	/// - `delegatee`: The account that the `delegator` would like to make a proxy.
892	/// - `proxy_type`: The permissions allowed for this proxy account.
893	/// - `delay`: The announcement period required of the initial proxy. Will generally be
894	/// zero.
895	pub fn remove_proxy_delegate(
896		delegator: &T::AccountId,
897		delegatee: T::AccountId,
898		proxy_type: T::ProxyType,
899		delay: BlockNumberFor<T>,
900	) -> DispatchResult {
901		Proxies::<T>::try_mutate_exists(delegator, |x| {
902			let (mut proxies, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
903			let proxy_def = ProxyDefinition {
904				delegate: delegatee.clone(),
905				proxy_type: proxy_type.clone(),
906				delay,
907			};
908			let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::<T>::NotFound)?;
909			proxies.remove(i);
910			let new_deposit = Self::deposit(proxies.len() as u32);
911			if new_deposit > old_deposit {
912				T::Currency::reserve(delegator, new_deposit - old_deposit)?;
913			} else if new_deposit < old_deposit {
914				T::Currency::unreserve(delegator, old_deposit - new_deposit);
915			}
916			if !proxies.is_empty() {
917				*x = Some((proxies, new_deposit))
918			}
919			Self::deposit_event(Event::<T>::ProxyRemoved {
920				delegator: delegator.clone(),
921				delegatee,
922				proxy_type,
923				delay,
924			});
925			Ok(())
926		})
927	}
928
929	pub fn deposit(num_proxies: u32) -> BalanceOf<T> {
930		if num_proxies == 0 {
931			Zero::zero()
932		} else {
933			T::ProxyDepositBase::get() + T::ProxyDepositFactor::get() * num_proxies.into()
934		}
935	}
936
937	fn rejig_deposit(
938		who: &T::AccountId,
939		old_deposit: BalanceOf<T>,
940		base: BalanceOf<T>,
941		factor: BalanceOf<T>,
942		len: usize,
943	) -> Result<Option<BalanceOf<T>>, DispatchError> {
944		let new_deposit =
945			if len == 0 { BalanceOf::<T>::zero() } else { base + factor * (len as u32).into() };
946		if new_deposit > old_deposit {
947			T::Currency::reserve(who, new_deposit.saturating_sub(old_deposit))?;
948		} else if new_deposit < old_deposit {
949			let excess = old_deposit.saturating_sub(new_deposit);
950			let remaining_unreserved = T::Currency::unreserve(who, excess);
951			if !remaining_unreserved.is_zero() {
952				defensive!(
953					"Failed to unreserve full amount. (Requested, Actual)",
954					(excess, excess.saturating_sub(remaining_unreserved))
955				);
956			}
957		}
958		Ok(if len == 0 { None } else { Some(new_deposit) })
959	}
960
961	fn edit_announcements<
962		F: FnMut(&Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>) -> bool,
963	>(
964		delegate: &T::AccountId,
965		f: F,
966	) -> DispatchResult {
967		Announcements::<T>::try_mutate_exists(delegate, |x| {
968			let (mut pending, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
969			let orig_pending_len = pending.len();
970			pending.retain(f);
971			ensure!(orig_pending_len > pending.len(), Error::<T>::NotFound);
972			*x = Self::rejig_deposit(
973				delegate,
974				old_deposit,
975				T::AnnouncementDepositBase::get(),
976				T::AnnouncementDepositFactor::get(),
977				pending.len(),
978			)?
979			.map(|deposit| (pending, deposit));
980			Ok(())
981		})
982	}
983
984	pub fn find_proxy(
985		real: &T::AccountId,
986		delegate: &T::AccountId,
987		force_proxy_type: Option<T::ProxyType>,
988	) -> Result<ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>, DispatchError> {
989		let f = |x: &ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>| -> bool {
990			&x.delegate == delegate
991				&& force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y)
992		};
993		Ok(Proxies::<T>::get(real).0.into_iter().find(f).ok_or(Error::<T>::NotProxy)?)
994	}
995
996	fn do_proxy(
997		def: ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>,
998		real: T::AccountId,
999		call: <T as Config>::RuntimeCall,
1000	) {
1001		use frame::traits::{InstanceFilter as _, OriginTrait as _};
1002		// This is a freshly authenticated new account, the origin restrictions doesn't apply.
1003		let mut origin: T::RuntimeOrigin = pezframe_system::RawOrigin::Signed(real).into();
1004		origin.add_filter(move |c: &<T as pezframe_system::Config>::RuntimeCall| {
1005			let c = <T as Config>::RuntimeCall::from_ref(c);
1006			// We make sure the proxy call does access this pezpallet to change modify proxies.
1007			match c.is_sub_type() {
1008				// Proxy call cannot add or remove a proxy with more permissions than it already
1009				// has.
1010				Some(Call::add_proxy { ref proxy_type, .. })
1011				| Some(Call::remove_proxy { ref proxy_type, .. })
1012					if !def.proxy_type.is_superset(proxy_type) =>
1013				{
1014					false
1015				},
1016				// Proxy call cannot remove all proxies or kill pure proxies unless it has full
1017				// permissions.
1018				Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. })
1019					if def.proxy_type != T::ProxyType::default() =>
1020				{
1021					false
1022				},
1023				_ => def.proxy_type.filter(c),
1024			}
1025		});
1026		let e = call.dispatch(origin);
1027		Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error) });
1028	}
1029
1030	/// Removes all proxy delegates for a given delegator.
1031	///
1032	/// Parameters:
1033	/// - `delegator`: The delegator account.
1034	pub fn remove_all_proxy_delegates(delegator: &T::AccountId) {
1035		let (proxies, old_deposit) = Proxies::<T>::take(delegator);
1036		T::Currency::unreserve(delegator, old_deposit);
1037		proxies.into_iter().for_each(|proxy_def| {
1038			Self::deposit_event(Event::<T>::ProxyRemoved {
1039				delegator: delegator.clone(),
1040				delegatee: proxy_def.delegate,
1041				proxy_type: proxy_def.proxy_type,
1042				delay: proxy_def.delay,
1043			});
1044		});
1045	}
1046}