Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[proxy-pallet] Introduce a transfer limit and duration for the AnyWithLimit proxy type #7938

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions substrate/frame/proxy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -75,6 +75,10 @@ pub struct ProxyDefinition<AccountId, ProxyType, BlockNumber> {
/// The number of blocks that an announcement must be in place for before the corresponding
/// call may be dispatched. If zero, then no announcement is needed.
pub delay: BlockNumber,
/// The maximun amount that can be transferred by this proxy.
pub max_amount: Option<BalanceOf<T>>,
/// The block number until which the limit is valid.
pub valid_until: BlockNumber,
}

/// Details surrounding a specific instance of an announcement to make a call.
@@ -163,6 +167,9 @@ pub mod pallet {
#[pallet::constant]
type MaxProxies: Get<u32>;

#[pallet::constant]
type MaxTransferAmount: Get<BalanceOf<Self>>;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

@@ -706,6 +713,10 @@ pub mod pallet {
Unannounced,
/// Cannot add self as proxy.
NoSelfProxy,
/// The transfer amoun exceeds the allowed limit.
TransferLimitExceeded,
/// The duration for the transfer limit has expired.
DurationExpired,
}

/// The set of account proxies. Maps the account which has delegated to the accounts
@@ -818,6 +829,8 @@ impl<T: Config> Pallet<T> {
delegatee: T::AccountId,
proxy_type: T::ProxyType,
delay: BlockNumberFor<T>,
max_amount: Option<BalanceOf<T>>,
valid_until: BlockNumberFor<T>
) -> DispatchResult {
ensure!(delegator != &delegatee, Error::<T>::NoSelfProxy);
Proxies::<T>::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| {
@@ -858,13 +871,25 @@ impl<T: Config> Pallet<T> {
delegatee: T::AccountId,
proxy_type: T::ProxyType,
delay: BlockNumberFor<T>,
max_amount: Option<BalanceOf<T>>,
valid_until: BlockNumberFor<T>,
) -> DispatchResult {
Proxies::<T>::try_mutate_exists(delegator, |x| {
let (mut proxies, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
let proxy_def = ProxyDefinition {
delegate: delegatee.clone(),
proxy_type: proxy_type.clone(),
delay,
max_amount: if proxy_type == ProxyType::AnyWithLimit {
max_amount
} else {
None
},
valid_until: if proxy_type == ProxyType::AnytWithLimit {
valid_unil
} else {
None
},
};
let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::<T>::NotFound)?;
proxies.remove(i);
@@ -977,7 +1002,22 @@ impl<T: Config> Pallet<T> {
Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. })
if def.proxy_type != T::ProxyType::default() =>
false,
_ => def.proxy_type.filter(c),

if def.proxy_type == ProxyType::AnyWithLimit {
if let Some(Call::Balances(BalancesCall::transfer_allow_death { value, .. })) = c {
// Check the proxy's max_amount limit
if let Some(max_amount) = def.max_amount {
if *value > max_amount {
return false; // Reject the call if it exceeds the proxy's limit
}
}
// Check the global MaxTransferAmount limit
if *value > T::MaxTransferAmount::get() {
return false; // Reject the call if it exceeds the global limit
}
}
}
def.proxy_type.filter(c),
}
});
let e = call.dispatch(origin);
@@ -992,4 +1032,4 @@ impl<T: Config> Pallet<T> {
let (_, old_deposit) = Proxies::<T>::take(&delegator);
T::Currency::unreserve(&delegator, old_deposit);
}
}
}
40 changes: 39 additions & 1 deletion substrate/frame/proxy/src/tests.rs
Original file line number Diff line number Diff line change
@@ -73,6 +73,7 @@ pub enum ProxyType {
Any,
JustTransfer,
JustUtility,
AnyWithLimit,
}
impl Default for ProxyType {
fn default() -> Self {
@@ -90,10 +91,21 @@ impl frame::traits::InstanceFilter<RuntimeCall> for ProxyType {
)
},
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
ProxyType::AnyWithLimit => {
if let RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { value, .. }) = c.is_sub_type() {
true
} else {
true
}
},
}
}
fn is_superset(&self, o: &Self) -> bool {
self == &ProxyType::Any || self == o
match (self, 0) {
(ProxyType::Any, _) => true,
(ProxyType::AnyWithLimit, ProxyType::AnyWithLimit) => true,
_ => self == 0,
}
}
}
pub struct BaseFilter;
@@ -823,3 +835,29 @@ fn poke_deposit_fails_for_unsigned_origin() {
assert_noop!(Proxy::poke_deposit(RuntimeOrigin::none()), DispatchError::BadOrigin,);
});
}

#[test]
fn set_transfer_limit_works() {
new_test_ext().execute_with(|| {
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::AnyWithLimit, 0));
assert_ok!(Proxy::set_transfer_limit(RuntimeOrigin::signed(1), 2, 100, 10));
let def = Proxies::<Test>::get(1).0[0].clone();
assert_eq!(def.max_amount, Some(100));
assert_eq!(def.valid_until, 10);
});
}

#[test]
fn transfer_with_limit_works() {
new_test_ext().execute_with(|| {
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::AnyWithLimit, 0));
assert_ok!(Proxy::set_transfer_limit(RuntimeOrigin::signed(1), 2, 100, 10));
let call = Box::new(call_transfer(6, 50));
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call));
let call = Box::new(call_transfer(6, 150));
assert_noop!(
Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call),
Error::<Test>::Unproxyable
);
});
}
Loading
Oops, something went wrong.