pallet_proxy/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
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 Pallet
19//! A pallet allowing accounts to give permission to other accounts to dispatch types of calls from
20//! 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, ReservableCurrency},
41};
42pub use pallet::*;
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 frame_system::Config>::AccountId>>::Balance;
49
50type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
51
52/// The parameters under which a particular account has a proxy relationship with some other
53/// account.
54#[derive(
55	Encode,
56	Decode,
57	Clone,
58	Copy,
59	Eq,
60	PartialEq,
61	Ord,
62	PartialOrd,
63	RuntimeDebug,
64	MaxEncodedLen,
65	TypeInfo,
66)]
67pub struct ProxyDefinition<AccountId, ProxyType, BlockNumber> {
68	/// The account which may act on behalf of another.
69	pub delegate: AccountId,
70	/// A value defining the subset of calls that it is allowed to make.
71	pub proxy_type: ProxyType,
72	/// The number of blocks that an announcement must be in place for before the corresponding
73	/// call may be dispatched. If zero, then no announcement is needed.
74	pub delay: BlockNumber,
75}
76
77/// Details surrounding a specific instance of an announcement to make a call.
78#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
79pub struct Announcement<AccountId, Hash, BlockNumber> {
80	/// The account which made the announcement.
81	real: AccountId,
82	/// The hash of the call to be made.
83	call_hash: Hash,
84	/// The height at which the announcement was made.
85	height: BlockNumber,
86}
87
88#[frame::pallet]
89pub mod pallet {
90	use super::*;
91
92	#[pallet::pallet]
93	pub struct Pallet<T>(_);
94
95	/// Configuration trait.
96	#[pallet::config]
97	pub trait Config: frame_system::Config {
98		/// The overarching event type.
99		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
100
101		/// The overarching call type.
102		type RuntimeCall: Parameter
103			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
104			+ GetDispatchInfo
105			+ From<frame_system::Call<Self>>
106			+ IsSubType<Call<Self>>
107			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
108
109		/// The currency mechanism.
110		type Currency: ReservableCurrency<Self::AccountId>;
111
112		/// A kind of proxy; specified with the proxy and passed in to the `IsProxyable` filter.
113		/// The instance filter determines whether a given call may be proxied under this type.
114		///
115		/// IMPORTANT: `Default` must be provided and MUST BE the the *most permissive* value.
116		type ProxyType: Parameter
117			+ Member
118			+ Ord
119			+ PartialOrd
120			+ frame::traits::InstanceFilter<<Self as Config>::RuntimeCall>
121			+ Default
122			+ MaxEncodedLen;
123
124		/// The base amount of currency needed to reserve for creating a proxy.
125		///
126		/// This is held for an additional storage item whose value size is
127		/// `sizeof(Balance)` bytes and whose key size is `sizeof(AccountId)` bytes.
128		#[pallet::constant]
129		type ProxyDepositBase: Get<BalanceOf<Self>>;
130
131		/// The amount of currency needed per proxy added.
132		///
133		/// This is held for adding 32 bytes plus an instance of `ProxyType` more into a
134		/// pre-existing storage value. Thus, when configuring `ProxyDepositFactor` one should take
135		/// into account `32 + proxy_type.encode().len()` bytes of data.
136		#[pallet::constant]
137		type ProxyDepositFactor: Get<BalanceOf<Self>>;
138
139		/// The maximum amount of proxies allowed for a single account.
140		#[pallet::constant]
141		type MaxProxies: Get<u32>;
142
143		/// Weight information for extrinsics in this pallet.
144		type WeightInfo: WeightInfo;
145
146		/// The maximum amount of time-delayed announcements that are allowed to be pending.
147		#[pallet::constant]
148		type MaxPending: Get<u32>;
149
150		/// The type of hash used for hashing the call.
151		type CallHasher: Hash;
152
153		/// The base amount of currency needed to reserve for creating an announcement.
154		///
155		/// This is held when a new storage item holding a `Balance` is created (typically 16
156		/// bytes).
157		#[pallet::constant]
158		type AnnouncementDepositBase: Get<BalanceOf<Self>>;
159
160		/// The amount of currency needed per announcement made.
161		///
162		/// This is held for adding an `AccountId`, `Hash` and `BlockNumber` (typically 68 bytes)
163		/// into a pre-existing storage value.
164		#[pallet::constant]
165		type AnnouncementDepositFactor: Get<BalanceOf<Self>>;
166	}
167
168	#[pallet::call]
169	impl<T: Config> Pallet<T> {
170		/// Dispatch the given `call` from an account that the sender is authorised for through
171		/// `add_proxy`.
172		///
173		/// The dispatch origin for this call must be _Signed_.
174		///
175		/// Parameters:
176		/// - `real`: The account that the proxy will make a call on behalf of.
177		/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
178		/// - `call`: The call to be made by the `real` account.
179		#[pallet::call_index(0)]
180		#[pallet::weight({
181			let di = call.get_dispatch_info();
182			(T::WeightInfo::proxy(T::MaxProxies::get())
183				 // AccountData for inner call origin accountdata.
184				.saturating_add(T::DbWeight::get().reads_writes(1, 1))
185				.saturating_add(di.call_weight),
186			di.class)
187		})]
188		pub fn proxy(
189			origin: OriginFor<T>,
190			real: AccountIdLookupOf<T>,
191			force_proxy_type: Option<T::ProxyType>,
192			call: Box<<T as Config>::RuntimeCall>,
193		) -> DispatchResult {
194			let who = ensure_signed(origin)?;
195			let real = T::Lookup::lookup(real)?;
196			let def = Self::find_proxy(&real, &who, force_proxy_type)?;
197			ensure!(def.delay.is_zero(), Error::<T>::Unannounced);
198
199			Self::do_proxy(def, real, *call);
200
201			Ok(())
202		}
203
204		/// Register a proxy account for the sender that is able to make calls on its behalf.
205		///
206		/// The dispatch origin for this call must be _Signed_.
207		///
208		/// Parameters:
209		/// - `proxy`: The account that the `caller` would like to make a proxy.
210		/// - `proxy_type`: The permissions allowed for this proxy account.
211		/// - `delay`: The announcement period required of the initial proxy. Will generally be
212		/// zero.
213		#[pallet::call_index(1)]
214		#[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))]
215		pub fn add_proxy(
216			origin: OriginFor<T>,
217			delegate: AccountIdLookupOf<T>,
218			proxy_type: T::ProxyType,
219			delay: BlockNumberFor<T>,
220		) -> DispatchResult {
221			let who = ensure_signed(origin)?;
222			let delegate = T::Lookup::lookup(delegate)?;
223			Self::add_proxy_delegate(&who, delegate, proxy_type, delay)
224		}
225
226		/// Unregister a proxy account for the sender.
227		///
228		/// The dispatch origin for this call must be _Signed_.
229		///
230		/// Parameters:
231		/// - `proxy`: The account that the `caller` would like to remove as a proxy.
232		/// - `proxy_type`: The permissions currently enabled for the removed proxy account.
233		#[pallet::call_index(2)]
234		#[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))]
235		pub fn remove_proxy(
236			origin: OriginFor<T>,
237			delegate: AccountIdLookupOf<T>,
238			proxy_type: T::ProxyType,
239			delay: BlockNumberFor<T>,
240		) -> DispatchResult {
241			let who = ensure_signed(origin)?;
242			let delegate = T::Lookup::lookup(delegate)?;
243			Self::remove_proxy_delegate(&who, delegate, proxy_type, delay)
244		}
245
246		/// Unregister all proxy accounts for the sender.
247		///
248		/// The dispatch origin for this call must be _Signed_.
249		///
250		/// WARNING: This may be called on accounts created by `pure`, however if done, then
251		/// the unreserved fees will be inaccessible. **All access to this account will be lost.**
252		#[pallet::call_index(3)]
253		#[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))]
254		pub fn remove_proxies(origin: OriginFor<T>) -> DispatchResult {
255			let who = ensure_signed(origin)?;
256			Self::remove_all_proxy_delegates(&who);
257			Ok(())
258		}
259
260		/// Spawn a fresh new account that is guaranteed to be otherwise inaccessible, and
261		/// initialize it with a proxy of `proxy_type` for `origin` sender.
262		///
263		/// Requires a `Signed` origin.
264		///
265		/// - `proxy_type`: The type of the proxy that the sender will be registered as over the
266		/// new account. This will almost always be the most permissive `ProxyType` possible to
267		/// allow for maximum flexibility.
268		/// - `index`: A disambiguation index, in case this is called multiple times in the same
269		/// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just
270		/// want to use `0`.
271		/// - `delay`: The announcement period required of the initial proxy. Will generally be
272		/// zero.
273		///
274		/// Fails with `Duplicate` if this has already been called in this transaction, from the
275		/// same sender, with the same parameters.
276		///
277		/// Fails if there are insufficient funds to pay for deposit.
278		#[pallet::call_index(4)]
279		#[pallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))]
280		pub fn create_pure(
281			origin: OriginFor<T>,
282			proxy_type: T::ProxyType,
283			delay: BlockNumberFor<T>,
284			index: u16,
285		) -> DispatchResult {
286			let who = ensure_signed(origin)?;
287
288			let pure = Self::pure_account(&who, &proxy_type, index, None);
289			ensure!(!Proxies::<T>::contains_key(&pure), Error::<T>::Duplicate);
290
291			let proxy_def =
292				ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay };
293			let bounded_proxies: BoundedVec<_, T::MaxProxies> =
294				vec![proxy_def].try_into().map_err(|_| Error::<T>::TooMany)?;
295
296			let deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get();
297			T::Currency::reserve(&who, deposit)?;
298
299			Proxies::<T>::insert(&pure, (bounded_proxies, deposit));
300			Self::deposit_event(Event::PureCreated {
301				pure,
302				who,
303				proxy_type,
304				disambiguation_index: index,
305			});
306
307			Ok(())
308		}
309
310		/// Removes a previously spawned pure proxy.
311		///
312		/// WARNING: **All access to this account will be lost.** Any funds held in it will be
313		/// inaccessible.
314		///
315		/// Requires a `Signed` origin, and the sender account must have been created by a call to
316		/// `pure` with corresponding parameters.
317		///
318		/// - `spawner`: The account that originally called `pure` to create this account.
319		/// - `index`: The disambiguation index originally passed to `pure`. Probably `0`.
320		/// - `proxy_type`: The proxy type originally passed to `pure`.
321		/// - `height`: The height of the chain when the call to `pure` was processed.
322		/// - `ext_index`: The extrinsic index in which the call to `pure` was processed.
323		///
324		/// Fails with `NoPermission` in case the caller is not a previously created pure
325		/// account whose `pure` call has corresponding parameters.
326		#[pallet::call_index(5)]
327		#[pallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))]
328		pub fn kill_pure(
329			origin: OriginFor<T>,
330			spawner: AccountIdLookupOf<T>,
331			proxy_type: T::ProxyType,
332			index: u16,
333			#[pallet::compact] height: BlockNumberFor<T>,
334			#[pallet::compact] ext_index: u32,
335		) -> DispatchResult {
336			let who = ensure_signed(origin)?;
337			let spawner = T::Lookup::lookup(spawner)?;
338
339			let when = (height, ext_index);
340			let proxy = Self::pure_account(&spawner, &proxy_type, index, Some(when));
341			ensure!(proxy == who, Error::<T>::NoPermission);
342
343			let (_, deposit) = Proxies::<T>::take(&who);
344			T::Currency::unreserve(&spawner, deposit);
345
346			Ok(())
347		}
348
349		/// Publish the hash of a proxy-call that will be made in the future.
350		///
351		/// This must be called some number of blocks before the corresponding `proxy` is attempted
352		/// if the delay associated with the proxy relationship is greater than zero.
353		///
354		/// No more than `MaxPending` announcements may be made at any one time.
355		///
356		/// This will take a deposit of `AnnouncementDepositFactor` as well as
357		/// `AnnouncementDepositBase` if there are no other pending announcements.
358		///
359		/// The dispatch origin for this call must be _Signed_ and a proxy of `real`.
360		///
361		/// Parameters:
362		/// - `real`: The account that the proxy will make a call on behalf of.
363		/// - `call_hash`: The hash of the call to be made by the `real` account.
364		#[pallet::call_index(6)]
365		#[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))]
366		pub fn announce(
367			origin: OriginFor<T>,
368			real: AccountIdLookupOf<T>,
369			call_hash: CallHashOf<T>,
370		) -> DispatchResult {
371			let who = ensure_signed(origin)?;
372			let real = T::Lookup::lookup(real)?;
373			Proxies::<T>::get(&real)
374				.0
375				.into_iter()
376				.find(|x| x.delegate == who)
377				.ok_or(Error::<T>::NotProxy)?;
378
379			let announcement = Announcement {
380				real: real.clone(),
381				call_hash,
382				height: frame_system::Pallet::<T>::block_number(),
383			};
384
385			Announcements::<T>::try_mutate(&who, |(ref mut pending, ref mut deposit)| {
386				pending.try_push(announcement).map_err(|_| Error::<T>::TooMany)?;
387				Self::rejig_deposit(
388					&who,
389					*deposit,
390					T::AnnouncementDepositBase::get(),
391					T::AnnouncementDepositFactor::get(),
392					pending.len(),
393				)
394				.map(|d| {
395					d.expect("Just pushed; pending.len() > 0; rejig_deposit returns Some; qed")
396				})
397				.map(|d| *deposit = d)
398			})?;
399			Self::deposit_event(Event::Announced { real, proxy: who, call_hash });
400
401			Ok(())
402		}
403
404		/// Remove a given announcement.
405		///
406		/// May be called by a proxy account to remove a call they previously announced and return
407		/// the deposit.
408		///
409		/// The dispatch origin for this call must be _Signed_.
410		///
411		/// Parameters:
412		/// - `real`: The account that the proxy will make a call on behalf of.
413		/// - `call_hash`: The hash of the call to be made by the `real` account.
414		#[pallet::call_index(7)]
415		#[pallet::weight(T::WeightInfo::remove_announcement(
416			T::MaxPending::get(),
417			T::MaxProxies::get()
418		))]
419		pub fn remove_announcement(
420			origin: OriginFor<T>,
421			real: AccountIdLookupOf<T>,
422			call_hash: CallHashOf<T>,
423		) -> DispatchResult {
424			let who = ensure_signed(origin)?;
425			let real = T::Lookup::lookup(real)?;
426			Self::edit_announcements(&who, |ann| ann.real != real || ann.call_hash != call_hash)?;
427
428			Ok(())
429		}
430
431		/// Remove the given announcement of a delegate.
432		///
433		/// May be called by a target (proxied) account to remove a call that one of their delegates
434		/// (`delegate`) has announced they want to execute. The deposit is returned.
435		///
436		/// The dispatch origin for this call must be _Signed_.
437		///
438		/// Parameters:
439		/// - `delegate`: The account that previously announced the call.
440		/// - `call_hash`: The hash of the call to be made.
441		#[pallet::call_index(8)]
442		#[pallet::weight(T::WeightInfo::reject_announcement(
443			T::MaxPending::get(),
444			T::MaxProxies::get()
445		))]
446		pub fn reject_announcement(
447			origin: OriginFor<T>,
448			delegate: AccountIdLookupOf<T>,
449			call_hash: CallHashOf<T>,
450		) -> DispatchResult {
451			let who = ensure_signed(origin)?;
452			let delegate = T::Lookup::lookup(delegate)?;
453			Self::edit_announcements(&delegate, |ann| {
454				ann.real != who || ann.call_hash != call_hash
455			})?;
456
457			Ok(())
458		}
459
460		/// Dispatch the given `call` from an account that the sender is authorized for through
461		/// `add_proxy`.
462		///
463		/// Removes any corresponding announcement(s).
464		///
465		/// The dispatch origin for this call must be _Signed_.
466		///
467		/// Parameters:
468		/// - `real`: The account that the proxy will make a call on behalf of.
469		/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
470		/// - `call`: The call to be made by the `real` account.
471		#[pallet::call_index(9)]
472		#[pallet::weight({
473			let di = call.get_dispatch_info();
474			(T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get())
475				 // AccountData for inner call origin accountdata.
476				.saturating_add(T::DbWeight::get().reads_writes(1, 1))
477				.saturating_add(di.call_weight),
478			di.class)
479		})]
480		pub fn proxy_announced(
481			origin: OriginFor<T>,
482			delegate: AccountIdLookupOf<T>,
483			real: AccountIdLookupOf<T>,
484			force_proxy_type: Option<T::ProxyType>,
485			call: Box<<T as Config>::RuntimeCall>,
486		) -> DispatchResult {
487			ensure_signed(origin)?;
488			let delegate = T::Lookup::lookup(delegate)?;
489			let real = T::Lookup::lookup(real)?;
490			let def = Self::find_proxy(&real, &delegate, force_proxy_type)?;
491
492			let call_hash = T::CallHasher::hash_of(&call);
493			let now = frame_system::Pallet::<T>::block_number();
494			Self::edit_announcements(&delegate, |ann| {
495				ann.real != real ||
496					ann.call_hash != call_hash ||
497					now.saturating_sub(ann.height) < def.delay
498			})
499			.map_err(|_| Error::<T>::Unannounced)?;
500
501			Self::do_proxy(def, real, *call);
502
503			Ok(())
504		}
505	}
506
507	#[pallet::event]
508	#[pallet::generate_deposit(pub(super) fn deposit_event)]
509	pub enum Event<T: Config> {
510		/// A proxy was executed correctly, with the given.
511		ProxyExecuted { result: DispatchResult },
512		/// A pure account has been created by new proxy with given
513		/// disambiguation index and proxy type.
514		PureCreated {
515			pure: T::AccountId,
516			who: T::AccountId,
517			proxy_type: T::ProxyType,
518			disambiguation_index: u16,
519		},
520		/// An announcement was placed to make a call in the future.
521		Announced { real: T::AccountId, proxy: T::AccountId, call_hash: CallHashOf<T> },
522		/// A proxy was added.
523		ProxyAdded {
524			delegator: T::AccountId,
525			delegatee: T::AccountId,
526			proxy_type: T::ProxyType,
527			delay: BlockNumberFor<T>,
528		},
529		/// A proxy was removed.
530		ProxyRemoved {
531			delegator: T::AccountId,
532			delegatee: T::AccountId,
533			proxy_type: T::ProxyType,
534			delay: BlockNumberFor<T>,
535		},
536	}
537
538	#[pallet::error]
539	pub enum Error<T> {
540		/// There are too many proxies registered or too many announcements pending.
541		TooMany,
542		/// Proxy registration not found.
543		NotFound,
544		/// Sender is not a proxy of the account to be proxied.
545		NotProxy,
546		/// A call which is incompatible with the proxy type's filter was attempted.
547		Unproxyable,
548		/// Account is already a proxy.
549		Duplicate,
550		/// Call may not be made by proxy because it may escalate its privileges.
551		NoPermission,
552		/// Announcement, if made at all, was made too recently.
553		Unannounced,
554		/// Cannot add self as proxy.
555		NoSelfProxy,
556	}
557
558	/// The set of account proxies. Maps the account which has delegated to the accounts
559	/// which are being delegated to, together with the amount held on deposit.
560	#[pallet::storage]
561	pub type Proxies<T: Config> = StorageMap<
562		_,
563		Twox64Concat,
564		T::AccountId,
565		(
566			BoundedVec<
567				ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>,
568				T::MaxProxies,
569			>,
570			BalanceOf<T>,
571		),
572		ValueQuery,
573	>;
574
575	/// The announcements made by the proxy (key).
576	#[pallet::storage]
577	pub type Announcements<T: Config> = StorageMap<
578		_,
579		Twox64Concat,
580		T::AccountId,
581		(
582			BoundedVec<Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>, T::MaxPending>,
583			BalanceOf<T>,
584		),
585		ValueQuery,
586	>;
587}
588
589impl<T: Config> Pallet<T> {
590	/// Public function to proxies storage.
591	pub fn proxies(
592		account: T::AccountId,
593	) -> (
594		BoundedVec<ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>, T::MaxProxies>,
595		BalanceOf<T>,
596	) {
597		Proxies::<T>::get(account)
598	}
599
600	/// Public function to announcements storage.
601	pub fn announcements(
602		account: T::AccountId,
603	) -> (
604		BoundedVec<Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>, T::MaxPending>,
605		BalanceOf<T>,
606	) {
607		Announcements::<T>::get(account)
608	}
609
610	/// Calculate the address of an pure account.
611	///
612	/// - `who`: The spawner account.
613	/// - `proxy_type`: The type of the proxy that the sender will be registered as over the
614	/// new account. This will almost always be the most permissive `ProxyType` possible to
615	/// allow for maximum flexibility.
616	/// - `index`: A disambiguation index, in case this is called multiple times in the same
617	/// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just
618	/// want to use `0`.
619	/// - `maybe_when`: The block height and extrinsic index of when the pure account was
620	/// created. None to use current block height and extrinsic index.
621	pub fn pure_account(
622		who: &T::AccountId,
623		proxy_type: &T::ProxyType,
624		index: u16,
625		maybe_when: Option<(BlockNumberFor<T>, u32)>,
626	) -> T::AccountId {
627		let (height, ext_index) = maybe_when.unwrap_or_else(|| {
628			(
629				frame_system::Pallet::<T>::block_number(),
630				frame_system::Pallet::<T>::extrinsic_index().unwrap_or_default(),
631			)
632		});
633		let entropy = (b"modlpy/proxy____", who, height, ext_index, proxy_type, index)
634			.using_encoded(blake2_256);
635		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
636			.expect("infinite length input; no invalid inputs for type; qed")
637	}
638
639	/// Register a proxy account for the delegator that is able to make calls on its behalf.
640	///
641	/// Parameters:
642	/// - `delegator`: The delegator account.
643	/// - `delegatee`: The account that the `delegator` would like to make a proxy.
644	/// - `proxy_type`: The permissions allowed for this proxy account.
645	/// - `delay`: The announcement period required of the initial proxy. Will generally be
646	/// zero.
647	pub fn add_proxy_delegate(
648		delegator: &T::AccountId,
649		delegatee: T::AccountId,
650		proxy_type: T::ProxyType,
651		delay: BlockNumberFor<T>,
652	) -> DispatchResult {
653		ensure!(delegator != &delegatee, Error::<T>::NoSelfProxy);
654		Proxies::<T>::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| {
655			let proxy_def = ProxyDefinition {
656				delegate: delegatee.clone(),
657				proxy_type: proxy_type.clone(),
658				delay,
659			};
660			let i = proxies.binary_search(&proxy_def).err().ok_or(Error::<T>::Duplicate)?;
661			proxies.try_insert(i, proxy_def).map_err(|_| Error::<T>::TooMany)?;
662			let new_deposit = Self::deposit(proxies.len() as u32);
663			if new_deposit > *deposit {
664				T::Currency::reserve(delegator, new_deposit - *deposit)?;
665			} else if new_deposit < *deposit {
666				T::Currency::unreserve(delegator, *deposit - new_deposit);
667			}
668			*deposit = new_deposit;
669			Self::deposit_event(Event::<T>::ProxyAdded {
670				delegator: delegator.clone(),
671				delegatee,
672				proxy_type,
673				delay,
674			});
675			Ok(())
676		})
677	}
678
679	/// Unregister a proxy account for the delegator.
680	///
681	/// Parameters:
682	/// - `delegator`: The delegator account.
683	/// - `delegatee`: The account that the `delegator` would like to make a proxy.
684	/// - `proxy_type`: The permissions allowed for this proxy account.
685	/// - `delay`: The announcement period required of the initial proxy. Will generally be
686	/// zero.
687	pub fn remove_proxy_delegate(
688		delegator: &T::AccountId,
689		delegatee: T::AccountId,
690		proxy_type: T::ProxyType,
691		delay: BlockNumberFor<T>,
692	) -> DispatchResult {
693		Proxies::<T>::try_mutate_exists(delegator, |x| {
694			let (mut proxies, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
695			let proxy_def = ProxyDefinition {
696				delegate: delegatee.clone(),
697				proxy_type: proxy_type.clone(),
698				delay,
699			};
700			let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::<T>::NotFound)?;
701			proxies.remove(i);
702			let new_deposit = Self::deposit(proxies.len() as u32);
703			if new_deposit > old_deposit {
704				T::Currency::reserve(delegator, new_deposit - old_deposit)?;
705			} else if new_deposit < old_deposit {
706				T::Currency::unreserve(delegator, old_deposit - new_deposit);
707			}
708			if !proxies.is_empty() {
709				*x = Some((proxies, new_deposit))
710			}
711			Self::deposit_event(Event::<T>::ProxyRemoved {
712				delegator: delegator.clone(),
713				delegatee,
714				proxy_type,
715				delay,
716			});
717			Ok(())
718		})
719	}
720
721	pub fn deposit(num_proxies: u32) -> BalanceOf<T> {
722		if num_proxies == 0 {
723			Zero::zero()
724		} else {
725			T::ProxyDepositBase::get() + T::ProxyDepositFactor::get() * num_proxies.into()
726		}
727	}
728
729	fn rejig_deposit(
730		who: &T::AccountId,
731		old_deposit: BalanceOf<T>,
732		base: BalanceOf<T>,
733		factor: BalanceOf<T>,
734		len: usize,
735	) -> Result<Option<BalanceOf<T>>, DispatchError> {
736		let new_deposit =
737			if len == 0 { BalanceOf::<T>::zero() } else { base + factor * (len as u32).into() };
738		if new_deposit > old_deposit {
739			T::Currency::reserve(who, new_deposit - old_deposit)?;
740		} else if new_deposit < old_deposit {
741			T::Currency::unreserve(who, old_deposit - new_deposit);
742		}
743		Ok(if len == 0 { None } else { Some(new_deposit) })
744	}
745
746	fn edit_announcements<
747		F: FnMut(&Announcement<T::AccountId, CallHashOf<T>, BlockNumberFor<T>>) -> bool,
748	>(
749		delegate: &T::AccountId,
750		f: F,
751	) -> DispatchResult {
752		Announcements::<T>::try_mutate_exists(delegate, |x| {
753			let (mut pending, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
754			let orig_pending_len = pending.len();
755			pending.retain(f);
756			ensure!(orig_pending_len > pending.len(), Error::<T>::NotFound);
757			*x = Self::rejig_deposit(
758				delegate,
759				old_deposit,
760				T::AnnouncementDepositBase::get(),
761				T::AnnouncementDepositFactor::get(),
762				pending.len(),
763			)?
764			.map(|deposit| (pending, deposit));
765			Ok(())
766		})
767	}
768
769	pub fn find_proxy(
770		real: &T::AccountId,
771		delegate: &T::AccountId,
772		force_proxy_type: Option<T::ProxyType>,
773	) -> Result<ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>, DispatchError> {
774		let f = |x: &ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>| -> bool {
775			&x.delegate == delegate &&
776				force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y)
777		};
778		Ok(Proxies::<T>::get(real).0.into_iter().find(f).ok_or(Error::<T>::NotProxy)?)
779	}
780
781	fn do_proxy(
782		def: ProxyDefinition<T::AccountId, T::ProxyType, BlockNumberFor<T>>,
783		real: T::AccountId,
784		call: <T as Config>::RuntimeCall,
785	) {
786		use frame::traits::{InstanceFilter as _, OriginTrait as _};
787		// This is a freshly authenticated new account, the origin restrictions doesn't apply.
788		let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into();
789		origin.add_filter(move |c: &<T as frame_system::Config>::RuntimeCall| {
790			let c = <T as Config>::RuntimeCall::from_ref(c);
791			// We make sure the proxy call does access this pallet to change modify proxies.
792			match c.is_sub_type() {
793				// Proxy call cannot add or remove a proxy with more permissions than it already
794				// has.
795				Some(Call::add_proxy { ref proxy_type, .. }) |
796				Some(Call::remove_proxy { ref proxy_type, .. })
797					if !def.proxy_type.is_superset(proxy_type) =>
798					false,
799				// Proxy call cannot remove all proxies or kill pure proxies unless it has full
800				// permissions.
801				Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. })
802					if def.proxy_type != T::ProxyType::default() =>
803					false,
804				_ => def.proxy_type.filter(c),
805			}
806		});
807		let e = call.dispatch(origin);
808		Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error) });
809	}
810
811	/// Removes all proxy delegates for a given delegator.
812	///
813	/// Parameters:
814	/// - `delegator`: The delegator account.
815	pub fn remove_all_proxy_delegates(delegator: &T::AccountId) {
816		let (_, old_deposit) = Proxies::<T>::take(&delegator);
817		T::Currency::unreserve(&delegator, old_deposit);
818	}
819}