1#![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#[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 pub delegate: AccountId,
70 pub proxy_type: ProxyType,
72 pub delay: BlockNumber,
75}
76
77#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
79pub struct Announcement<AccountId, Hash, BlockNumber> {
80 real: AccountId,
82 call_hash: Hash,
84 height: BlockNumber,
86}
87
88#[frame::pallet]
89pub mod pallet {
90 use super::*;
91
92 #[pallet::pallet]
93 pub struct Pallet<T>(_);
94
95 #[pallet::config]
97 pub trait Config: frame_system::Config {
98 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
100
101 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 type Currency: ReservableCurrency<Self::AccountId>;
111
112 type ProxyType: Parameter
117 + Member
118 + Ord
119 + PartialOrd
120 + frame::traits::InstanceFilter<<Self as Config>::RuntimeCall>
121 + Default
122 + MaxEncodedLen;
123
124 #[pallet::constant]
129 type ProxyDepositBase: Get<BalanceOf<Self>>;
130
131 #[pallet::constant]
137 type ProxyDepositFactor: Get<BalanceOf<Self>>;
138
139 #[pallet::constant]
141 type MaxProxies: Get<u32>;
142
143 type WeightInfo: WeightInfo;
145
146 #[pallet::constant]
148 type MaxPending: Get<u32>;
149
150 type CallHasher: Hash;
152
153 #[pallet::constant]
158 type AnnouncementDepositBase: Get<BalanceOf<Self>>;
159
160 #[pallet::constant]
165 type AnnouncementDepositFactor: Get<BalanceOf<Self>>;
166 }
167
168 #[pallet::call]
169 impl<T: Config> Pallet<T> {
170 #[pallet::call_index(0)]
180 #[pallet::weight({
181 let di = call.get_dispatch_info();
182 (T::WeightInfo::proxy(T::MaxProxies::get())
183 .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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 .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 ProxyExecuted { result: DispatchResult },
512 PureCreated {
515 pure: T::AccountId,
516 who: T::AccountId,
517 proxy_type: T::ProxyType,
518 disambiguation_index: u16,
519 },
520 Announced { real: T::AccountId, proxy: T::AccountId, call_hash: CallHashOf<T> },
522 ProxyAdded {
524 delegator: T::AccountId,
525 delegatee: T::AccountId,
526 proxy_type: T::ProxyType,
527 delay: BlockNumberFor<T>,
528 },
529 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 TooMany,
542 NotFound,
544 NotProxy,
546 Unproxyable,
548 Duplicate,
550 NoPermission,
552 Unannounced,
554 NoSelfProxy,
556 }
557
558 #[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 #[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 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 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 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 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 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 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 match c.is_sub_type() {
793 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 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 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}