Skip to content

Commit c0dace7

Browse files
committed
feat(permission0): new wallet scope
1 parent 8ccfed1 commit c0dace7

File tree

12 files changed

+1000
-12
lines changed

12 files changed

+1000
-12
lines changed

.helix/languages.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[language-server.rust-analyzer.config]
22
cargo.extraEnv = { SKIP_WASM_BUILD = "true" }
33
cargo.features = ["runtime-benchmarks"]
4-
4+
check.extraEnv = { SKIP_WASM_BUILD = "true" }
5+
check.overrideCommand = ["cargo", "check", "--message-format=json"]

pallets/permission0/api/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,29 @@ pub trait Permission0NamespacesApi<AccountId, NamespacePath> {
161161
fn is_delegating_namespace(delegator: &AccountId, path: &NamespacePath) -> bool;
162162
}
163163

164+
pub struct WalletPermission<AccountId> {
165+
pub recipient: AccountId,
166+
pub r#type: WalletScopeType,
167+
}
168+
169+
pub enum WalletScopeType {
170+
Stake {
171+
/// If true, allows the recipient to perform transfer of stake between staked accounts.
172+
can_transfer_stake: bool,
173+
/// If true, this permission holds exclusive access to the delegator stake, meaning that
174+
/// the delegator has no right to perform operations over stake (including unstaking)
175+
/// while this permission is active.
176+
exclusive_stake_access: bool,
177+
},
178+
}
179+
180+
pub trait Permission0WalletApi<AccountId> {
181+
/// Lists all active wallet permissions, regardless of the type.
182+
fn find_active_wallet_permission(
183+
delegator: &AccountId,
184+
) -> impl Iterator<Item = (PermissionId, WalletPermission<AccountId>)>;
185+
}
186+
164187
polkadot_sdk::sp_api::decl_runtime_apis! {
165188
/// A set of helper functions for permission and streams
166189
/// queries.

pallets/permission0/src/ext.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use polkadot_sdk::{
1717
pub mod curator_impl;
1818
pub mod namespace_impl;
1919
pub mod stream_impl;
20+
pub mod wallet_impl;
2021

2122
/// Implementation of the Permission0Api trait to be used externally
2223
impl<T: Config> Permission0Api<OriginFor<T>> for pallet::Pallet<T> {
@@ -146,6 +147,7 @@ pub(crate) fn execute_permission_impl<T: Config>(
146147
}
147148
PermissionScope::Curator(_) => curator_impl::execute_permission_impl::<T>(permission_id),
148149
PermissionScope::Namespace(_) => Ok(()),
150+
PermissionScope::Wallet(_) => Ok(()),
149151
}
150152
}
151153

@@ -208,6 +210,7 @@ pub fn enforcement_execute_permission_impl<T: Config>(
208210
return curator_impl::execute_permission_impl::<T>(&permission_id);
209211
}
210212
PermissionScope::Namespace(_) => return Ok(()),
213+
PermissionScope::Wallet(_) => return Ok(()),
211214
}
212215

213216
EnforcementTracking::<T>::remove(permission_id, EnforcementReferendum::Execution);
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use codec::{Decode, Encode, MaxEncodedLen};
2+
use pallet_permission0_api::Permission0WalletApi;
3+
use pallet_torus0_api::Torus0Api;
4+
use polkadot_sdk::{
5+
frame_support::{
6+
CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound, dispatch::DispatchResult, ensure,
7+
},
8+
frame_system::ensure_signed,
9+
polkadot_sdk_frame::prelude::OriginFor,
10+
};
11+
use scale_info::TypeInfo;
12+
13+
use crate::{
14+
BalanceOf, Config, Error, Event, Pallet, PermissionContract, PermissionDuration, PermissionId,
15+
PermissionScope, Permissions, PermissionsByDelegator, RevocationTerms, generate_permission_id,
16+
permission::{
17+
add_permission_indices,
18+
wallet::{WalletScope, WalletScopeType, WalletStake},
19+
},
20+
};
21+
22+
impl<T: Config> Permission0WalletApi<T::AccountId> for Pallet<T> {
23+
fn find_active_wallet_permission(
24+
delegator: &T::AccountId,
25+
) -> impl Iterator<
26+
Item = (
27+
PermissionId,
28+
pallet_permission0_api::WalletPermission<T::AccountId>,
29+
),
30+
> {
31+
PermissionsByDelegator::<T>::get(delegator)
32+
.into_iter()
33+
.filter_map(|pid| {
34+
let permission = Permissions::<T>::get(pid)?;
35+
let PermissionScope::Wallet(wallet) = permission.scope else {
36+
return None;
37+
};
38+
39+
Some((
40+
pid,
41+
pallet_permission0_api::WalletPermission {
42+
recipient: wallet.recipient,
43+
r#type: match wallet.r#type {
44+
WalletScopeType::Stake(stake) => {
45+
pallet_permission0_api::WalletScopeType::Stake {
46+
can_transfer_stake: stake.can_transfer_stake,
47+
exclusive_stake_access: stake.exclusive_stake_access,
48+
}
49+
}
50+
},
51+
},
52+
))
53+
})
54+
}
55+
}
56+
pub(crate) fn delegate_wallet_stake_permission<T: Config>(
57+
origin: OriginFor<T>,
58+
recipient: T::AccountId,
59+
stake_details: WalletStake,
60+
duration: PermissionDuration<T>,
61+
revocation: RevocationTerms<T>,
62+
) -> DispatchResult {
63+
let delegator = ensure_signed(origin)?;
64+
ensure!(delegator != recipient, Error::<T>::SelfPermissionNotAllowed);
65+
66+
for (_, perm) in Pallet::<T>::find_active_wallet_permission(&delegator) {
67+
if stake_details.exclusive_stake_access
68+
|| matches!(
69+
perm.r#type,
70+
pallet_permission0_api::WalletScopeType::Stake {
71+
exclusive_stake_access: true,
72+
..
73+
}
74+
)
75+
{
76+
return Err(Error::<T>::DuplicatePermission.into());
77+
}
78+
}
79+
80+
let scope = PermissionScope::Wallet(WalletScope {
81+
recipient: recipient.clone(),
82+
r#type: WalletScopeType::Stake(stake_details),
83+
});
84+
let permission_id = generate_permission_id::<T>(&delegator, &scope)?;
85+
86+
let contract = PermissionContract::<T>::new(
87+
delegator,
88+
scope,
89+
duration,
90+
revocation,
91+
crate::EnforcementAuthority::None,
92+
);
93+
94+
Permissions::<T>::insert(permission_id, &contract);
95+
add_permission_indices::<T>(
96+
&contract.delegator,
97+
core::iter::once(&recipient),
98+
permission_id,
99+
)?;
100+
101+
<Pallet<T>>::deposit_event(Event::PermissionDelegated {
102+
delegator: contract.delegator,
103+
permission_id,
104+
});
105+
106+
Ok(())
107+
}
108+
109+
pub(crate) fn execute_wallet_stake_permission<T: Config>(
110+
caller: OriginFor<T>,
111+
permission_id: PermissionId,
112+
op: WalletStakeOperation<T>,
113+
) -> DispatchResult {
114+
let caller = ensure_signed(caller)?;
115+
let Some(permission) = Permissions::<T>::get(permission_id) else {
116+
return Err(Error::<T>::PermissionNotFound.into());
117+
};
118+
let PermissionScope::Wallet(wallet) = &permission.scope else {
119+
return Err(Error::<T>::UnsupportedPermissionType.into());
120+
};
121+
#[allow(irrefutable_let_patterns)]
122+
let WalletScopeType::Stake(stake) = &wallet.r#type else {
123+
return Err(Error::<T>::UnsupportedPermissionType.into());
124+
};
125+
126+
ensure!(
127+
caller == wallet.recipient,
128+
Error::<T>::NotPermissionRecipient
129+
);
130+
131+
let staker = &permission.delegator;
132+
133+
match op {
134+
WalletStakeOperation::Unstake { staked, amount } => {
135+
<T::Torus>::remove_stake(staker, &staked, amount)?;
136+
}
137+
WalletStakeOperation::Transfer { from, to, amount } => {
138+
ensure!(stake.can_transfer_stake, Error::<T>::PermissionNotFound);
139+
<T::Torus>::transfer_stake(staker, &from, &to, amount)?;
140+
}
141+
}
142+
143+
Ok(())
144+
}
145+
146+
#[derive(
147+
CloneNoBound, DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEqNoBound, EqNoBound,
148+
)]
149+
#[scale_info(skip_type_params(T))]
150+
pub enum WalletStakeOperation<T: Config> {
151+
/// Unstakes the balance from the staked account, yielding control of the
152+
/// balance back to the delegator.
153+
Unstake {
154+
staked: T::AccountId,
155+
amount: BalanceOf<T>,
156+
},
157+
/// Transfers stake from one staked agent to another staked agent,
158+
/// related to the `transfer_stake` extrinsic in Torus0.
159+
Transfer {
160+
from: T::AccountId,
161+
to: T::AccountId,
162+
amount: BalanceOf<T>,
163+
},
164+
}

pallets/permission0/src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use polkadot_sdk::{
3434
#[frame::pallet]
3535
pub mod pallet {
3636
use pallet_torus0_api::NamespacePathInner;
37+
use permission::wallet::WalletStake;
3738
use polkadot_sdk::{frame_support::PalletId, sp_core::TryCollect};
3839

3940
use super::*;
@@ -491,6 +492,27 @@ pub mod pallet {
491492
Ok(())
492493
}
493494

495+
/// Delegate a permission over namespaces
496+
#[pallet::call_index(11)]
497+
#[pallet::weight(T::WeightInfo::delegate_namespace_permission())]
498+
pub fn delegate_wallet_stake_permission(
499+
origin: OriginFor<T>,
500+
recipient: T::AccountId,
501+
stake_details: WalletStake,
502+
duration: PermissionDuration<T>,
503+
revocation: RevocationTerms<T>,
504+
) -> DispatchResult {
505+
ext::wallet_impl::delegate_wallet_stake_permission::<T>(
506+
origin,
507+
recipient,
508+
stake_details,
509+
duration,
510+
revocation,
511+
)?;
512+
513+
Ok(())
514+
}
515+
494516
/// Delegate a permission over namespaces to multiple recipients.
495517
/// Note: this extrinsic creates _multiple_ permissions with the same
496518
/// properties.
@@ -568,6 +590,16 @@ pub mod pallet {
568590

569591
Ok(())
570592
}
593+
594+
#[pallet::call_index(12)]
595+
#[pallet::weight(T::WeightInfo::update_namespace_permission())]
596+
pub fn execute_wallet_stake_permission(
597+
caller: OriginFor<T>,
598+
permission_id: PermissionId,
599+
op: ext::wallet_impl::WalletStakeOperation<T>,
600+
) -> DispatchResult {
601+
ext::wallet_impl::execute_wallet_stake_permission(caller, permission_id, op)
602+
}
571603
}
572604
}
573605

pallets/permission0/src/permission.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ use polkadot_sdk::{
1111
BoundedBTreeMap, BoundedVec, DispatchError, Percent,
1212
traits::{BlakeTwo256, Hash},
1313
},
14-
sp_std::vec::Vec,
14+
sp_std::{vec, vec::Vec},
1515
sp_tracing::{error, info, trace},
1616
};
1717
use scale_info::TypeInfo;
18+
use wallet::WalletScope;
1819

1920
use crate::*;
2021

@@ -25,6 +26,7 @@ pub use stream::{DistributionControl, StreamAllocation, StreamScope};
2526
pub mod curator;
2627
pub mod namespace;
2728
pub mod stream;
29+
pub mod wallet;
2830

2931
/// Type for permission ID
3032
pub type PermissionId = H256;
@@ -246,7 +248,8 @@ impl<T: Config> PermissionContract<T> {
246248
let delegator = self.delegator.clone();
247249
let recipients = match &self.scope {
248250
PermissionScope::Curator(CuratorScope { recipient, .. })
249-
| PermissionScope::Namespace(NamespaceScope { recipient, .. }) => {
251+
| PermissionScope::Namespace(NamespaceScope { recipient, .. })
252+
| PermissionScope::Wallet(WalletScope { recipient, .. }) => {
250253
vec![recipient.clone()]
251254
}
252255
PermissionScope::Stream(StreamScope { recipients, .. }) => {
@@ -292,7 +295,7 @@ impl<T: Config> PermissionContract<T> {
292295
Error::<T>::NotAuthorizedToRevoke
293296
);
294297

295-
if recipients.len() > 1 {
298+
if recipients.len() > 1usize {
296299
remove_recipient_from_indices::<T>(&delegator, caller, permission_id);
297300

298301
Permissions::<T>::mutate(permission_id, |permission| {
@@ -347,7 +350,8 @@ impl<T: Config> PermissionContract<T> {
347350
fn cleanup(self, permission_id: H256) -> DispatchResult {
348351
match &self.scope {
349352
PermissionScope::Curator(CuratorScope { recipient, .. })
350-
| PermissionScope::Namespace(NamespaceScope { recipient, .. }) => {
353+
| PermissionScope::Namespace(NamespaceScope { recipient, .. })
354+
| PermissionScope::Wallet(WalletScope { recipient, .. }) => {
351355
remove_permission_from_indices::<T>(
352356
&self.delegator,
353357
core::iter::once(recipient),
@@ -377,6 +381,9 @@ impl<T: Config> PermissionContract<T> {
377381
PermissionScope::Namespace(namespace) => {
378382
namespace.cleanup(permission_id, &self.last_execution, &self.delegator);
379383
}
384+
PermissionScope::Wallet(wallet) => {
385+
wallet.cleanup(permission_id, &self.last_execution, &self.delegator);
386+
}
380387
}
381388

382389
Ok(())
@@ -394,6 +401,7 @@ pub enum PermissionScope<T: Config> {
394401
Stream(StreamScope<T>),
395402
Curator(CuratorScope<T>),
396403
Namespace(NamespaceScope<T>),
404+
Wallet(WalletScope<T>),
397405
}
398406

399407
#[derive(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use codec::{Decode, Encode, MaxEncodedLen};
2+
use polkadot_sdk::frame_support::{CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound};
3+
use scale_info::TypeInfo;
4+
5+
use crate::Config;
6+
7+
#[derive(CloneNoBound, DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)]
8+
#[scale_info(skip_type_params(T))]
9+
pub struct WalletScope<T: Config> {
10+
pub recipient: T::AccountId,
11+
pub r#type: WalletScopeType,
12+
}
13+
14+
impl<T: Config> WalletScope<T> {
15+
/// Cleanup operations when permission is revoked or expired
16+
pub(crate) fn cleanup(
17+
&self,
18+
_permission_id: polkadot_sdk::sp_core::H256,
19+
_last_execution: &Option<crate::BlockNumberFor<T>>,
20+
_delegator: &T::AccountId,
21+
) {
22+
// No actions to perform
23+
}
24+
}
25+
26+
#[derive(CloneNoBound, DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)]
27+
pub enum WalletScopeType {
28+
Stake(WalletStake),
29+
}
30+
31+
#[derive(
32+
CloneNoBound, DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEqNoBound, EqNoBound,
33+
)]
34+
pub struct WalletStake {
35+
/// If true, allows the recipient to perform transfer of stake between staked accounts.
36+
pub can_transfer_stake: bool,
37+
/// If true, this permission holds exclusive access to the delegator stake, meaning that
38+
/// the delegator has no right to perform operations over stake (including unstaking)
39+
/// while this permission is active.
40+
pub exclusive_stake_access: bool,
41+
}

0 commit comments

Comments
 (0)