1#![cfg_attr(not(feature = "std"), no_std)]
22
23mod benchmarking;
24mod mock;
25mod tests;
26pub mod weights;
27
28extern crate alloc;
29
30use alloc::vec::Vec;
31use codec::Codec;
32use frame_support::traits::{BalanceStatus::Reserved, Currency, ReservableCurrency};
33use sp_runtime::{
34 traits::{AtLeast32Bit, LookupError, Saturating, StaticLookup, Zero},
35 MultiAddress,
36};
37pub use weights::WeightInfo;
38
39type BalanceOf<T> =
40 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
41type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
42
43pub use pallet::*;
44
45#[frame_support::pallet]
46pub mod pallet {
47 use super::*;
48 use frame_support::pallet_prelude::*;
49 use frame_system::pallet_prelude::*;
50
51 #[pallet::config]
53 pub trait Config: frame_system::Config {
54 type AccountIndex: Parameter
57 + Member
58 + MaybeSerializeDeserialize
59 + Codec
60 + Default
61 + AtLeast32Bit
62 + Copy
63 + MaxEncodedLen;
64
65 type Currency: ReservableCurrency<Self::AccountId>;
67
68 #[pallet::constant]
70 type Deposit: Get<BalanceOf<Self>>;
71
72 #[allow(deprecated)]
74 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
75
76 type WeightInfo: WeightInfo;
78 }
79
80 #[pallet::pallet]
81 pub struct Pallet<T>(_);
82
83 #[pallet::call]
84 impl<T: Config> Pallet<T> {
85 #[pallet::call_index(0)]
98 #[pallet::weight(T::WeightInfo::claim())]
99 pub fn claim(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
100 let who = ensure_signed(origin)?;
101
102 Accounts::<T>::try_mutate(index, |maybe_value| {
103 ensure!(maybe_value.is_none(), Error::<T>::InUse);
104 *maybe_value = Some((who.clone(), T::Deposit::get(), false));
105 T::Currency::reserve(&who, T::Deposit::get())
106 })?;
107 Self::deposit_event(Event::IndexAssigned { who, index });
108 Ok(())
109 }
110
111 #[pallet::call_index(1)]
124 #[pallet::weight(T::WeightInfo::transfer())]
125 pub fn transfer(
126 origin: OriginFor<T>,
127 new: AccountIdLookupOf<T>,
128 index: T::AccountIndex,
129 ) -> DispatchResult {
130 let who = ensure_signed(origin)?;
131 let new = T::Lookup::lookup(new)?;
132 ensure!(who != new, Error::<T>::NotTransfer);
133
134 Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
135 let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
136 ensure!(!perm, Error::<T>::Permanent);
137 ensure!(account == who, Error::<T>::NotOwner);
138 let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?;
139 *maybe_value = Some((new.clone(), amount.saturating_sub(lost), false));
140 Ok(())
141 })?;
142 Self::deposit_event(Event::IndexAssigned { who: new, index });
143 Ok(())
144 }
145
146 #[pallet::call_index(2)]
159 #[pallet::weight(T::WeightInfo::free())]
160 pub fn free(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
161 let who = ensure_signed(origin)?;
162
163 Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
164 let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
165 ensure!(!perm, Error::<T>::Permanent);
166 ensure!(account == who, Error::<T>::NotOwner);
167 T::Currency::unreserve(&who, amount);
168 Ok(())
169 })?;
170 Self::deposit_event(Event::IndexFreed { index });
171 Ok(())
172 }
173
174 #[pallet::call_index(3)]
188 #[pallet::weight(T::WeightInfo::force_transfer())]
189 pub fn force_transfer(
190 origin: OriginFor<T>,
191 new: AccountIdLookupOf<T>,
192 index: T::AccountIndex,
193 freeze: bool,
194 ) -> DispatchResult {
195 ensure_root(origin)?;
196 let new = T::Lookup::lookup(new)?;
197
198 Accounts::<T>::mutate(index, |maybe_value| {
199 if let Some((account, amount, _)) = maybe_value.take() {
200 T::Currency::unreserve(&account, amount);
201 }
202 *maybe_value = Some((new.clone(), Zero::zero(), freeze));
203 });
204 Self::deposit_event(Event::IndexAssigned { who: new, index });
205 Ok(())
206 }
207
208 #[pallet::call_index(4)]
221 #[pallet::weight(T::WeightInfo::freeze())]
222 pub fn freeze(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
223 let who = ensure_signed(origin)?;
224
225 Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
226 let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
227 ensure!(!perm, Error::<T>::Permanent);
228 ensure!(account == who, Error::<T>::NotOwner);
229 let _ = T::Currency::slash_reserved(&who, amount);
230 *maybe_value = Some((account, Zero::zero(), true));
231 Ok(())
232 })?;
233 Self::deposit_event(Event::IndexFrozen { index, who });
234 Ok(())
235 }
236
237 #[pallet::call_index(5)]
248 #[pallet::weight(T::WeightInfo::poke_deposit())]
249 pub fn poke_deposit(
250 origin: OriginFor<T>,
251 index: T::AccountIndex,
252 ) -> DispatchResultWithPostInfo {
253 let who = ensure_signed(origin)?;
254
255 Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResultWithPostInfo {
256 let (account, old_amount, perm) =
257 maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
258 ensure!(!perm, Error::<T>::Permanent);
259 ensure!(account == who, Error::<T>::NotOwner);
260
261 let new_amount = T::Deposit::get();
262
263 if old_amount == new_amount {
264 *maybe_value = Some((account, old_amount, perm));
265 return Ok(Pays::Yes.into());
266 } else if new_amount > old_amount {
267 let extra = new_amount.saturating_sub(old_amount);
269 T::Currency::reserve(&who, extra)?;
270 } else if new_amount < old_amount {
271 let excess = old_amount.saturating_sub(new_amount);
273 let remaining_unreserved = T::Currency::unreserve(&who, excess);
274 if !remaining_unreserved.is_zero() {
276 defensive!(
277 "Failed to unreserve full amount. (Index, Requested, Actual): ",
278 (index, excess, excess - remaining_unreserved)
279 );
280 }
281 }
282
283 *maybe_value = Some((account, new_amount, perm));
284
285 Self::deposit_event(Event::DepositPoked {
286 who,
287 index,
288 old_deposit: old_amount,
289 new_deposit: new_amount,
290 });
291 Ok(Pays::No.into())
292 })
293 }
294 }
295
296 #[pallet::event]
297 #[pallet::generate_deposit(pub(super) fn deposit_event)]
298 pub enum Event<T: Config> {
299 IndexAssigned { who: T::AccountId, index: T::AccountIndex },
301 IndexFreed { index: T::AccountIndex },
303 IndexFrozen { index: T::AccountIndex, who: T::AccountId },
305 DepositPoked {
307 who: T::AccountId,
308 index: T::AccountIndex,
309 old_deposit: BalanceOf<T>,
310 new_deposit: BalanceOf<T>,
311 },
312 }
313
314 #[pallet::error]
315 pub enum Error<T> {
316 NotAssigned,
318 NotOwner,
320 InUse,
322 NotTransfer,
324 Permanent,
326 }
327
328 #[pallet::storage]
330 pub type Accounts<T: Config> =
331 StorageMap<_, Blake2_128Concat, T::AccountIndex, (T::AccountId, BalanceOf<T>, bool)>;
332
333 #[pallet::genesis_config]
334 #[derive(frame_support::DefaultNoBound)]
335 pub struct GenesisConfig<T: Config> {
336 pub indices: Vec<(T::AccountIndex, T::AccountId)>,
337 }
338
339 #[pallet::genesis_build]
340 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
341 fn build(&self) {
342 for (a, b) in &self.indices {
343 <Accounts<T>>::insert(a, (b, <BalanceOf<T>>::zero(), false))
344 }
345 }
346 }
347}
348
349impl<T: Config> Pallet<T> {
350 pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
354 Accounts::<T>::get(index).map(|x| x.0)
355 }
356
357 pub fn lookup_address(a: MultiAddress<T::AccountId, T::AccountIndex>) -> Option<T::AccountId> {
359 match a {
360 MultiAddress::Id(i) => Some(i),
361 MultiAddress::Index(i) => Self::lookup_index(i),
362 _ => None,
363 }
364 }
365}
366
367impl<T: Config> StaticLookup for Pallet<T> {
368 type Source = MultiAddress<T::AccountId, T::AccountIndex>;
369 type Target = T::AccountId;
370
371 fn lookup(a: Self::Source) -> Result<Self::Target, LookupError> {
372 Self::lookup_address(a).ok_or(LookupError)
373 }
374
375 fn unlookup(a: Self::Target) -> Self::Source {
376 MultiAddress::Id(a)
377 }
378}