referrerpolicy=no-referrer-when-downgrade

pallet_indices/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! An index is a short form of an address. This module handles allocation
19//! of indices for a newly created accounts.
20
21#![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	/// The module's config trait.
52	#[pallet::config]
53	pub trait Config: frame_system::Config {
54		/// Type used for storing an account's index; implies the maximum number of accounts the
55		/// system can hold.
56		type AccountIndex: Parameter
57			+ Member
58			+ MaybeSerializeDeserialize
59			+ Codec
60			+ Default
61			+ AtLeast32Bit
62			+ Copy
63			+ MaxEncodedLen;
64
65		/// The currency trait.
66		type Currency: ReservableCurrency<Self::AccountId>;
67
68		/// The deposit needed for reserving an index.
69		#[pallet::constant]
70		type Deposit: Get<BalanceOf<Self>>;
71
72		/// The overarching event type.
73		#[allow(deprecated)]
74		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
75
76		/// Weight information for extrinsics in this pallet.
77		type WeightInfo: WeightInfo;
78	}
79
80	#[pallet::pallet]
81	pub struct Pallet<T>(_);
82
83	#[pallet::call]
84	impl<T: Config> Pallet<T> {
85		/// Assign an previously unassigned index.
86		///
87		/// Payment: `Deposit` is reserved from the sender account.
88		///
89		/// The dispatch origin for this call must be _Signed_.
90		///
91		/// - `index`: the index to be claimed. This must not be in use.
92		///
93		/// Emits `IndexAssigned` if successful.
94		///
95		/// ## Complexity
96		/// - `O(1)`.
97		#[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		/// Assign an index already owned by the sender to another account. The balance reservation
112		/// is effectively transferred to the new account.
113		///
114		/// The dispatch origin for this call must be _Signed_.
115		///
116		/// - `index`: the index to be re-assigned. This must be owned by the sender.
117		/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
118		///
119		/// Emits `IndexAssigned` if successful.
120		///
121		/// ## Complexity
122		/// - `O(1)`.
123		#[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		/// Free up an index owned by the sender.
147		///
148		/// Payment: Any previous deposit placed for the index is unreserved in the sender account.
149		///
150		/// The dispatch origin for this call must be _Signed_ and the sender must own the index.
151		///
152		/// - `index`: the index to be freed. This must be owned by the sender.
153		///
154		/// Emits `IndexFreed` if successful.
155		///
156		/// ## Complexity
157		/// - `O(1)`.
158		#[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		/// Force an index to an account. This doesn't require a deposit. If the index is already
175		/// held, then any deposit is reimbursed to its current owner.
176		///
177		/// The dispatch origin for this call must be _Root_.
178		///
179		/// - `index`: the index to be (re-)assigned.
180		/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
181		/// - `freeze`: if set to `true`, will freeze the index so it cannot be transferred.
182		///
183		/// Emits `IndexAssigned` if successful.
184		///
185		/// ## Complexity
186		/// - `O(1)`.
187		#[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		/// Freeze an index so it will always point to the sender account. This consumes the
209		/// deposit.
210		///
211		/// The dispatch origin for this call must be _Signed_ and the signing account must have a
212		/// non-frozen account `index`.
213		///
214		/// - `index`: the index to be frozen in place.
215		///
216		/// Emits `IndexFrozen` if successful.
217		///
218		/// ## Complexity
219		/// - `O(1)`.
220		#[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		/// Poke the deposit reserved for an index.
238		///
239		/// The dispatch origin for this call must be _Signed_ and the signing account must have a
240		/// non-frozen account `index`.
241		///
242		/// The transaction fees is waived if the deposit is changed after poking/reconsideration.
243		///
244		/// - `index`: the index whose deposit is to be poked/reconsidered.
245		///
246		/// Emits `DepositPoked` if successful.
247		#[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					// Need to reserve more
268					let extra = new_amount.saturating_sub(old_amount);
269					T::Currency::reserve(&who, extra)?;
270				} else if new_amount < old_amount {
271					// Need to unreserve some
272					let excess = old_amount.saturating_sub(new_amount);
273					let remaining_unreserved = T::Currency::unreserve(&who, excess);
274					// Defensive logging if we can't unreserve the full amount.
275					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		/// A account index was assigned.
300		IndexAssigned { who: T::AccountId, index: T::AccountIndex },
301		/// A account index has been freed up (unassigned).
302		IndexFreed { index: T::AccountIndex },
303		/// A account index has been frozen to its current account ID.
304		IndexFrozen { index: T::AccountIndex, who: T::AccountId },
305		/// A deposit to reserve an index has been poked/reconsidered.
306		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		/// The index was not already assigned.
317		NotAssigned,
318		/// The index is assigned to another account.
319		NotOwner,
320		/// The index was not available.
321		InUse,
322		/// The source and destination accounts are identical.
323		NotTransfer,
324		/// The index is permanent and may not be freed/changed.
325		Permanent,
326	}
327
328	/// The lookup from index to account.
329	#[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	// PUBLIC IMMUTABLES
351
352	/// Lookup an T::AccountIndex to get an Id, if there's one there.
353	pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
354		Accounts::<T>::get(index).map(|x| x.0)
355	}
356
357	/// Lookup an address to get an Id, if there's one there.
358	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}