pallet_permission0/ext/
wallet_impl.rs

1use codec::{Decode, Encode, MaxEncodedLen};
2use pallet_permission0_api::Permission0WalletApi;
3use pallet_torus0_api::Torus0Api;
4use 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};
11use scale_info::TypeInfo;
12
13use 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
22impl<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}
56pub(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
109pub(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))]
150pub 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}