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		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
74
75		/// Weight information for extrinsics in this pallet.
76		type WeightInfo: WeightInfo;
77	}
78
79	#[pallet::pallet]
80	pub struct Pallet<T>(_);
81
82	#[pallet::call]
83	impl<T: Config> Pallet<T> {
84		/// Assign an previously unassigned index.
85		///
86		/// Payment: `Deposit` is reserved from the sender account.
87		///
88		/// The dispatch origin for this call must be _Signed_.
89		///
90		/// - `index`: the index to be claimed. This must not be in use.
91		///
92		/// Emits `IndexAssigned` if successful.
93		///
94		/// ## Complexity
95		/// - `O(1)`.
96		#[pallet::call_index(0)]
97		#[pallet::weight(T::WeightInfo::claim())]
98		pub fn claim(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
99			let who = ensure_signed(origin)?;
100
101			Accounts::<T>::try_mutate(index, |maybe_value| {
102				ensure!(maybe_value.is_none(), Error::<T>::InUse);
103				*maybe_value = Some((who.clone(), T::Deposit::get(), false));
104				T::Currency::reserve(&who, T::Deposit::get())
105			})?;
106			Self::deposit_event(Event::IndexAssigned { who, index });
107			Ok(())
108		}
109
110		/// Assign an index already owned by the sender to another account. The balance reservation
111		/// is effectively transferred to the new account.
112		///
113		/// The dispatch origin for this call must be _Signed_.
114		///
115		/// - `index`: the index to be re-assigned. This must be owned by the sender.
116		/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
117		///
118		/// Emits `IndexAssigned` if successful.
119		///
120		/// ## Complexity
121		/// - `O(1)`.
122		#[pallet::call_index(1)]
123		#[pallet::weight(T::WeightInfo::transfer())]
124		pub fn transfer(
125			origin: OriginFor<T>,
126			new: AccountIdLookupOf<T>,
127			index: T::AccountIndex,
128		) -> DispatchResult {
129			let who = ensure_signed(origin)?;
130			let new = T::Lookup::lookup(new)?;
131			ensure!(who != new, Error::<T>::NotTransfer);
132
133			Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
134				let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
135				ensure!(!perm, Error::<T>::Permanent);
136				ensure!(account == who, Error::<T>::NotOwner);
137				let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?;
138				*maybe_value = Some((new.clone(), amount.saturating_sub(lost), false));
139				Ok(())
140			})?;
141			Self::deposit_event(Event::IndexAssigned { who: new, index });
142			Ok(())
143		}
144
145		/// Free up an index owned by the sender.
146		///
147		/// Payment: Any previous deposit placed for the index is unreserved in the sender account.
148		///
149		/// The dispatch origin for this call must be _Signed_ and the sender must own the index.
150		///
151		/// - `index`: the index to be freed. This must be owned by the sender.
152		///
153		/// Emits `IndexFreed` if successful.
154		///
155		/// ## Complexity
156		/// - `O(1)`.
157		#[pallet::call_index(2)]
158		#[pallet::weight(T::WeightInfo::free())]
159		pub fn free(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
160			let who = ensure_signed(origin)?;
161
162			Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
163				let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
164				ensure!(!perm, Error::<T>::Permanent);
165				ensure!(account == who, Error::<T>::NotOwner);
166				T::Currency::unreserve(&who, amount);
167				Ok(())
168			})?;
169			Self::deposit_event(Event::IndexFreed { index });
170			Ok(())
171		}
172
173		/// Force an index to an account. This doesn't require a deposit. If the index is already
174		/// held, then any deposit is reimbursed to its current owner.
175		///
176		/// The dispatch origin for this call must be _Root_.
177		///
178		/// - `index`: the index to be (re-)assigned.
179		/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
180		/// - `freeze`: if set to `true`, will freeze the index so it cannot be transferred.
181		///
182		/// Emits `IndexAssigned` if successful.
183		///
184		/// ## Complexity
185		/// - `O(1)`.
186		#[pallet::call_index(3)]
187		#[pallet::weight(T::WeightInfo::force_transfer())]
188		pub fn force_transfer(
189			origin: OriginFor<T>,
190			new: AccountIdLookupOf<T>,
191			index: T::AccountIndex,
192			freeze: bool,
193		) -> DispatchResult {
194			ensure_root(origin)?;
195			let new = T::Lookup::lookup(new)?;
196
197			Accounts::<T>::mutate(index, |maybe_value| {
198				if let Some((account, amount, _)) = maybe_value.take() {
199					T::Currency::unreserve(&account, amount);
200				}
201				*maybe_value = Some((new.clone(), Zero::zero(), freeze));
202			});
203			Self::deposit_event(Event::IndexAssigned { who: new, index });
204			Ok(())
205		}
206
207		/// Freeze an index so it will always point to the sender account. This consumes the
208		/// deposit.
209		///
210		/// The dispatch origin for this call must be _Signed_ and the signing account must have a
211		/// non-frozen account `index`.
212		///
213		/// - `index`: the index to be frozen in place.
214		///
215		/// Emits `IndexFrozen` if successful.
216		///
217		/// ## Complexity
218		/// - `O(1)`.
219		#[pallet::call_index(4)]
220		#[pallet::weight(T::WeightInfo::freeze())]
221		pub fn freeze(origin: OriginFor<T>, index: T::AccountIndex) -> DispatchResult {
222			let who = ensure_signed(origin)?;
223
224			Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
225				let (account, amount, perm) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
226				ensure!(!perm, Error::<T>::Permanent);
227				ensure!(account == who, Error::<T>::NotOwner);
228				let _ = T::Currency::slash_reserved(&who, amount);
229				*maybe_value = Some((account, Zero::zero(), true));
230				Ok(())
231			})?;
232			Self::deposit_event(Event::IndexFrozen { index, who });
233			Ok(())
234		}
235
236		/// Poke the deposit reserved for an index.
237		///
238		/// The dispatch origin for this call must be _Signed_ and the signing account must have a
239		/// non-frozen account `index`.
240		///
241		/// The transaction fees is waived if the deposit is changed after poking/reconsideration.
242		///
243		/// - `index`: the index whose deposit is to be poked/reconsidered.
244		///
245		/// Emits `DepositPoked` if successful.
246		#[pallet::call_index(5)]
247		#[pallet::weight(T::WeightInfo::poke_deposit())]
248		pub fn poke_deposit(
249			origin: OriginFor<T>,
250			index: T::AccountIndex,
251		) -> DispatchResultWithPostInfo {
252			let who = ensure_signed(origin)?;
253
254			Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResultWithPostInfo {
255				let (account, old_amount, perm) =
256					maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
257				ensure!(!perm, Error::<T>::Permanent);
258				ensure!(account == who, Error::<T>::NotOwner);
259
260				let new_amount = T::Deposit::get();
261
262				if old_amount == new_amount {
263					*maybe_value = Some((account, old_amount, perm));
264					return Ok(Pays::Yes.into());
265				} else if new_amount > old_amount {
266					// Need to reserve more
267					let extra = new_amount.saturating_sub(old_amount);
268					T::Currency::reserve(&who, extra)?;
269				} else if new_amount < old_amount {
270					// Need to unreserve some
271					let excess = old_amount.saturating_sub(new_amount);
272					let remaining_unreserved = T::Currency::unreserve(&who, excess);
273					// Defensive logging if we can't unreserve the full amount.
274					if !remaining_unreserved.is_zero() {
275						defensive!(
276							"Failed to unreserve full amount. (Index, Requested, Actual): ",
277							(index, excess, excess - remaining_unreserved)
278						);
279					}
280				}
281
282				*maybe_value = Some((account, new_amount, perm));
283
284				Self::deposit_event(Event::DepositPoked {
285					who,
286					index,
287					old_deposit: old_amount,
288					new_deposit: new_amount,
289				});
290				Ok(Pays::No.into())
291			})
292		}
293	}
294
295	#[pallet::event]
296	#[pallet::generate_deposit(pub(super) fn deposit_event)]
297	pub enum Event<T: Config> {
298		/// A account index was assigned.
299		IndexAssigned { who: T::AccountId, index: T::AccountIndex },
300		/// A account index has been freed up (unassigned).
301		IndexFreed { index: T::AccountIndex },
302		/// A account index has been frozen to its current account ID.
303		IndexFrozen { index: T::AccountIndex, who: T::AccountId },
304		/// A deposit to reserve an index has been poked/reconsidered.
305		DepositPoked {
306			who: T::AccountId,
307			index: T::AccountIndex,
308			old_deposit: BalanceOf<T>,
309			new_deposit: BalanceOf<T>,
310		},
311	}
312
313	#[pallet::error]
314	pub enum Error<T> {
315		/// The index was not already assigned.
316		NotAssigned,
317		/// The index is assigned to another account.
318		NotOwner,
319		/// The index was not available.
320		InUse,
321		/// The source and destination accounts are identical.
322		NotTransfer,
323		/// The index is permanent and may not be freed/changed.
324		Permanent,
325	}
326
327	/// The lookup from index to account.
328	#[pallet::storage]
329	pub type Accounts<T: Config> =
330		StorageMap<_, Blake2_128Concat, T::AccountIndex, (T::AccountId, BalanceOf<T>, bool)>;
331
332	#[pallet::genesis_config]
333	#[derive(frame_support::DefaultNoBound)]
334	pub struct GenesisConfig<T: Config> {
335		pub indices: Vec<(T::AccountIndex, T::AccountId)>,
336	}
337
338	#[pallet::genesis_build]
339	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
340		fn build(&self) {
341			for (a, b) in &self.indices {
342				<Accounts<T>>::insert(a, (b, <BalanceOf<T>>::zero(), false))
343			}
344		}
345	}
346}
347
348impl<T: Config> Pallet<T> {
349	// PUBLIC IMMUTABLES
350
351	/// Lookup an T::AccountIndex to get an Id, if there's one there.
352	pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
353		Accounts::<T>::get(index).map(|x| x.0)
354	}
355
356	/// Lookup an address to get an Id, if there's one there.
357	pub fn lookup_address(a: MultiAddress<T::AccountId, T::AccountIndex>) -> Option<T::AccountId> {
358		match a {
359			MultiAddress::Id(i) => Some(i),
360			MultiAddress::Index(i) => Self::lookup_index(i),
361			_ => None,
362		}
363	}
364}
365
366impl<T: Config> StaticLookup for Pallet<T> {
367	type Source = MultiAddress<T::AccountId, T::AccountIndex>;
368	type Target = T::AccountId;
369
370	fn lookup(a: Self::Source) -> Result<Self::Target, LookupError> {
371		Self::lookup_address(a).ok_or(LookupError)
372	}
373
374	fn unlookup(a: Self::Target) -> Self::Source {
375		MultiAddress::Id(a)
376	}
377}