Skip to content

Commit a3b3812

Browse files
committed
introduce a transfer limit and duration for the AnyWithLimit proxy type
1 parent 3a6c489 commit a3b3812

File tree

2 files changed

+101
-139
lines changed

2 files changed

+101
-139
lines changed

substrate/frame/proxy/src/lib.rs

+62-138
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ pub struct ProxyDefinition<AccountId, ProxyType, BlockNumber> {
7575
/// The number of blocks that an announcement must be in place for before the corresponding
7676
/// call may be dispatched. If zero, then no announcement is needed.
7777
pub delay: BlockNumber,
78+
/// The maximun amount that can be transferred by this proxy.
79+
pub max_amount: Option<BalanceOf<T>>,
80+
/// The block number until which the limit is valid.
81+
pub valid_unil: BlockNumber,
7882
}
7983

8084
/// Details surrounding a specific instance of an announcement to make a call.
@@ -88,26 +92,6 @@ pub struct Announcement<AccountId, Hash, BlockNumber> {
8892
height: BlockNumber,
8993
}
9094

91-
/// The type of deposit
92-
#[derive(
93-
Encode,
94-
Decode,
95-
Clone,
96-
Copy,
97-
Eq,
98-
PartialEq,
99-
RuntimeDebug,
100-
MaxEncodedLen,
101-
TypeInfo,
102-
DecodeWithMemTracking,
103-
)]
104-
pub enum DepositKind {
105-
/// Proxy registration deposit
106-
Proxies,
107-
/// Announcement deposit
108-
Announcements,
109-
}
110-
11195
#[frame::pallet]
11296
pub mod pallet {
11397
use super::*;
@@ -170,6 +154,10 @@ pub mod pallet {
170154
#[pallet::constant]
171155
type MaxPending: Get<u32>;
172156

157+
#[pallet::constant]
158+
type MaxTransferAmount: Get<BalanceOf<Self>>;
159+
160+
173161
/// The type of hash used for hashing the call.
174162
type CallHasher: Hash;
175163

@@ -264,10 +252,12 @@ pub mod pallet {
264252
delegate: AccountIdLookupOf<T>,
265253
proxy_type: T::ProxyType,
266254
delay: BlockNumberFor<T>,
255+
max_amount: Option<BalanceOf<T>>,
256+
valid_unil: BlockNumberFor<T>,
267257
) -> DispatchResult {
268258
let who = ensure_signed(origin)?;
269259
let delegate = T::Lookup::lookup(delegate)?;
270-
Self::add_proxy_delegate(&who, delegate, proxy_type, delay)
260+
Self::add_proxy_delegate(&who, delegate, proxy_type, delay, max_amount, valid_unil)
271261
}
272262

273263
/// Unregister a proxy account for the sender.
@@ -329,14 +319,16 @@ pub mod pallet {
329319
proxy_type: T::ProxyType,
330320
delay: BlockNumberFor<T>,
331321
index: u16,
322+
max_amount: Option<BalanceOf<T>>,
323+
valid_unil: BlockNumberFor<T>,
332324
) -> DispatchResult {
333325
let who = ensure_signed(origin)?;
334326

335327
let pure = Self::pure_account(&who, &proxy_type, index, None);
336328
ensure!(!Proxies::<T>::contains_key(&pure), Error::<T>::Duplicate);
337329

338330
let proxy_def =
339-
ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay };
331+
ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay, max_amount, valid_unil };
340332
let bounded_proxies: BoundedVec<_, T::MaxProxies> =
341333
vec![proxy_def].try_into().map_err(|_| Error::<T>::TooMany)?;
342334

@@ -549,105 +541,6 @@ pub mod pallet {
549541

550542
Ok(())
551543
}
552-
553-
/// Poke / Adjust deposits made for proxies and announcements based on current values.
554-
/// This can be used by accounts to possibly lower their locked amount.
555-
///
556-
/// The dispatch origin for this call must be _Signed_.
557-
///
558-
/// The transaction fee is waived if the deposit amount has changed.
559-
///
560-
/// Emits `DepositPoked` if successful.
561-
#[pallet::call_index(10)]
562-
#[pallet::weight(T::WeightInfo::poke_deposit())]
563-
pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
564-
let who = ensure_signed(origin)?;
565-
let mut deposit_updated = false;
566-
567-
// Check and update proxy deposits
568-
Proxies::<T>::try_mutate_exists(&who, |maybe_proxies| -> DispatchResult {
569-
let (proxies, old_deposit) = maybe_proxies.take().unwrap_or_default();
570-
let maybe_new_deposit = Self::rejig_deposit(
571-
&who,
572-
old_deposit,
573-
T::ProxyDepositBase::get(),
574-
T::ProxyDepositFactor::get(),
575-
proxies.len(),
576-
)?;
577-
578-
match maybe_new_deposit {
579-
Some(new_deposit) if new_deposit != old_deposit => {
580-
*maybe_proxies = Some((proxies, new_deposit));
581-
deposit_updated = true;
582-
Self::deposit_event(Event::DepositPoked {
583-
who: who.clone(),
584-
kind: DepositKind::Proxies,
585-
old_deposit,
586-
new_deposit,
587-
});
588-
},
589-
Some(_) => {
590-
*maybe_proxies = Some((proxies, old_deposit));
591-
},
592-
None => {
593-
*maybe_proxies = None;
594-
if !old_deposit.is_zero() {
595-
deposit_updated = true;
596-
Self::deposit_event(Event::DepositPoked {
597-
who: who.clone(),
598-
kind: DepositKind::Proxies,
599-
old_deposit,
600-
new_deposit: BalanceOf::<T>::zero(),
601-
});
602-
}
603-
},
604-
}
605-
Ok(())
606-
})?;
607-
608-
// Check and update announcement deposits
609-
Announcements::<T>::try_mutate_exists(&who, |maybe_announcements| -> DispatchResult {
610-
let (announcements, old_deposit) = maybe_announcements.take().unwrap_or_default();
611-
let maybe_new_deposit = Self::rejig_deposit(
612-
&who,
613-
old_deposit,
614-
T::AnnouncementDepositBase::get(),
615-
T::AnnouncementDepositFactor::get(),
616-
announcements.len(),
617-
)?;
618-
619-
match maybe_new_deposit {
620-
Some(new_deposit) if new_deposit != old_deposit => {
621-
*maybe_announcements = Some((announcements, new_deposit));
622-
deposit_updated = true;
623-
Self::deposit_event(Event::DepositPoked {
624-
who: who.clone(),
625-
kind: DepositKind::Announcements,
626-
old_deposit,
627-
new_deposit,
628-
});
629-
},
630-
Some(_) => {
631-
*maybe_announcements = Some((announcements, old_deposit));
632-
},
633-
None => {
634-
*maybe_announcements = None;
635-
if !old_deposit.is_zero() {
636-
deposit_updated = true;
637-
Self::deposit_event(Event::DepositPoked {
638-
who: who.clone(),
639-
kind: DepositKind::Announcements,
640-
old_deposit,
641-
new_deposit: BalanceOf::<T>::zero(),
642-
});
643-
}
644-
},
645-
}
646-
Ok(())
647-
})?;
648-
649-
Ok(if deposit_updated { Pays::No.into() } else { Pays::Yes.into() })
650-
}
651544
}
652545

653546
#[pallet::event]
@@ -679,13 +572,6 @@ pub mod pallet {
679572
proxy_type: T::ProxyType,
680573
delay: BlockNumberFor<T>,
681574
},
682-
/// A deposit stored for proxies or announcements was poked / updated.
683-
DepositPoked {
684-
who: T::AccountId,
685-
kind: DepositKind,
686-
old_deposit: BalanceOf<T>,
687-
new_deposit: BalanceOf<T>,
688-
},
689575
}
690576

691577
#[pallet::error]
@@ -706,6 +592,10 @@ pub mod pallet {
706592
Unannounced,
707593
/// Cannot add self as proxy.
708594
NoSelfProxy,
595+
/// The transfer amoun exceeds the allowed limit.
596+
TransferLimitExceeded,
597+
/// The duration for the transfer limit has expired.
598+
DurationExpired,
709599
}
710600

711601
/// The set of account proxies. Maps the account which has delegated to the accounts
@@ -818,13 +708,25 @@ impl<T: Config> Pallet<T> {
818708
delegatee: T::AccountId,
819709
proxy_type: T::ProxyType,
820710
delay: BlockNumberFor<T>,
711+
max_amount: Option<BalanceOf<T>>,
712+
valid_unil: BlockNumberFor<T>,
821713
) -> DispatchResult {
822714
ensure!(delegator != &delegatee, Error::<T>::NoSelfProxy);
823715
Proxies::<T>::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| {
824716
let proxy_def = ProxyDefinition {
825717
delegate: delegatee.clone(),
826718
proxy_type: proxy_type.clone(),
827719
delay,
720+
max_amount: if proxy_type == ProxyType::AnyWithLimit {
721+
max_amount
722+
} else {
723+
None
724+
},
725+
valid_unil: if proxy_type = ProxyType::AnyWithLimit {
726+
valid_unil
727+
} else {
728+
None
729+
},
828730
};
829731
let i = proxies.binary_search(&proxy_def).err().ok_or(Error::<T>::Duplicate)?;
830732
proxies.try_insert(i, proxy_def).map_err(|_| Error::<T>::TooMany)?;
@@ -858,13 +760,25 @@ impl<T: Config> Pallet<T> {
858760
delegatee: T::AccountId,
859761
proxy_type: T::ProxyType,
860762
delay: BlockNumberFor<T>,
763+
max_amount: Option<BalanceOf<T>>,
764+
valid_until: BlockNumberFor<T>,
861765
) -> DispatchResult {
862766
Proxies::<T>::try_mutate_exists(delegator, |x| {
863767
let (mut proxies, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
864768
let proxy_def = ProxyDefinition {
865769
delegate: delegatee.clone(),
866770
proxy_type: proxy_type.clone(),
867771
delay,
772+
max_amount: if proxy_type == ProxyType::AnyWithLimit {
773+
max_amount
774+
} else {
775+
None
776+
},
777+
valid_until: if proxy_type == ProxyType::AnytWithLimit {
778+
valid_unil
779+
} else {
780+
None
781+
},
868782
};
869783
let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::<T>::NotFound)?;
870784
proxies.remove(i);
@@ -905,16 +819,9 @@ impl<T: Config> Pallet<T> {
905819
let new_deposit =
906820
if len == 0 { BalanceOf::<T>::zero() } else { base + factor * (len as u32).into() };
907821
if new_deposit > old_deposit {
908-
T::Currency::reserve(who, new_deposit.saturating_sub(old_deposit))?;
822+
T::Currency::reserve(who, new_deposit - old_deposit)?;
909823
} else if new_deposit < old_deposit {
910-
let excess = old_deposit.saturating_sub(new_deposit);
911-
let remaining_unreserved = T::Currency::unreserve(who, excess);
912-
if !remaining_unreserved.is_zero() {
913-
defensive!(
914-
"Failed to unreserve full amount. (Requested, Actual)",
915-
(excess, excess.saturating_sub(remaining_unreserved))
916-
);
917-
}
824+
T::Currency::unreserve(who, old_deposit - new_deposit);
918825
}
919826
Ok(if len == 0 { None } else { Some(new_deposit) })
920827
}
@@ -977,7 +884,24 @@ impl<T: Config> Pallet<T> {
977884
Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. })
978885
if def.proxy_type != T::ProxyType::default() =>
979886
false,
980-
_ => def.proxy_type.filter(c),
887+
// Enforce the max_amount and duration limits for `AnyWithLimit` proxies.
888+
if def.proxy_type == ProxyType::AnyWithLimit {
889+
if let Some(Call::Balances(BalancesCall::transfer_allow_death { value, .. })) = c.is_sub_type() {
890+
if let Some(max_amount) = def.max_amount {
891+
if *value > max_amount {
892+
return false;
893+
}
894+
}
895+
// Check the duration limit
896+
if let Some(duration) = def.duration {
897+
let current_block = T::BlockNumberProvider::current_block_number();
898+
if current_block > def.delay + valid_unil {
899+
return false;
900+
}
901+
}
902+
}
903+
def.proxy_type.filter(c),
904+
}
981905
}
982906
});
983907
let e = call.dispatch(origin);

substrate/frame/proxy/src/tests.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub enum ProxyType {
7373
Any,
7474
JustTransfer,
7575
JustUtility,
76+
AnyWithLimit,
7677
}
7778
impl Default for ProxyType {
7879
fn default() -> Self {
@@ -90,10 +91,21 @@ impl frame::traits::InstanceFilter<RuntimeCall> for ProxyType {
9091
)
9192
},
9293
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
94+
ProxyType::AnyWithLimit => {
95+
if let RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { value, .. }) = c.is_sub_type() {
96+
true
97+
} else {
98+
true
99+
}
100+
},
93101
}
94102
}
95103
fn is_superset(&self, o: &Self) -> bool {
96-
self == &ProxyType::Any || self == o
104+
match (self, 0) {
105+
(ProxyType::Any, _) => true,
106+
(ProxyType::AnyWithLimit, ProxyType::AnyWithLimit) => true,
107+
_ => self == 0,
108+
}
97109
}
98110
}
99111
pub struct BaseFilter;
@@ -823,3 +835,29 @@ fn poke_deposit_fails_for_unsigned_origin() {
823835
assert_noop!(Proxy::poke_deposit(RuntimeOrigin::none()), DispatchError::BadOrigin,);
824836
});
825837
}
838+
839+
#[test]
840+
fn set_transfer_limit_works() {
841+
new_test_ext().execute_with(|| {
842+
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::AnyWithLimit, 0));
843+
assert_ok!(Proxy::set_transfer_limit(RuntimeOrigin::signed(1), 2, 100, 10));
844+
let def = Proxies::<Test>::get(1).0[0].clone();
845+
assert_eq!(def.max_amount, Some(100));
846+
assert_eq!(def.valid_until, 10);
847+
});
848+
}
849+
850+
#[test]
851+
fn transfer_with_limit_works() {
852+
new_test_ext().execute_with(|| {
853+
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::AnyWithLimit, 0));
854+
assert_ok!(Proxy::set_transfer_limit(RuntimeOrigin::signed(1), 2, 100, 10));
855+
let call = Box::new(call_transfer(6, 50));
856+
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call));
857+
let call = Box::new(call_transfer(6, 150));
858+
assert_noop!(
859+
Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call),
860+
Error::<Test>::Unproxyable
861+
);
862+
});
863+
}

0 commit comments

Comments
 (0)