// Copyright 2017-2019 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . //! An index is a short form of an address. This module handles allocation //! of indices for a newly created accounts. #![cfg_attr(not(feature = "std"), no_std)] use rstd::{prelude::*, marker::PhantomData, convert::TryInto}; use codec::{Encode, Codec}; use support::{Parameter, decl_module, decl_event, decl_storage}; use sr_primitives::traits::{One, SimpleArithmetic, StaticLookup, Member, LookupError}; use system::{IsDeadAccount, OnNewAccount}; use self::address::Address as RawAddress; mod mock; pub mod address; mod tests; /// Number of account IDs stored per enum set. const ENUM_SET_SIZE: u32 = 64; pub type Address = RawAddress<::AccountId, ::AccountIndex>; /// Turn an Id into an Index, or None for the purpose of getting /// a hint at a possibly desired index. pub trait ResolveHint { /// Turn an Id into an Index, or None for the purpose of getting /// a hint at a possibly desired index. fn resolve_hint(who: &AccountId) -> Option; } /// Simple encode-based resolve hint implementation. pub struct SimpleResolveHint(PhantomData<(AccountId, AccountIndex)>); impl> ResolveHint for SimpleResolveHint { fn resolve_hint(who: &AccountId) -> Option { Some(AccountIndex::from(who.using_encoded(|e| e[0] as u32 + e[1] as u32 * 256))) } } /// The module's config trait. pub trait Trait: system::Trait { /// Type used for storing an account's index; implies the maximum number of accounts the system /// can hold. type AccountIndex: Parameter + Member + Codec + Default + SimpleArithmetic + Copy; /// Whether an account is dead or not. type IsDeadAccount: IsDeadAccount; /// How to turn an id into an index. type ResolveHint: ResolveHint; /// The overarching event type. type Event: From> + Into<::Event>; } decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; } } decl_event!( pub enum Event where ::AccountId, ::AccountIndex { /// A new account index was assigned. /// /// This event is not triggered when an existing index is reassigned /// to another `AccountId`. NewAccountIndex(AccountId, AccountIndex), } ); decl_storage! { trait Store for Module as Indices { /// The next free enumeration set. pub NextEnumSet get(next_enum_set) build(|config: &GenesisConfig| { (config.ids.len() as u32 / ENUM_SET_SIZE).into() }): T::AccountIndex; /// The enumeration sets. pub EnumSet get(enum_set) build(|config: &GenesisConfig| { (0..((config.ids.len() as u32) + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE) .map(|i| ( i.into(), config.ids[ (i * ENUM_SET_SIZE) as usize.. config.ids.len().min(((i + 1) * ENUM_SET_SIZE) as usize) ].to_owned(), )) .collect::>() }): map T::AccountIndex => Vec; } add_extra_genesis { config(ids): Vec; } } impl Module { // PUBLIC IMMUTABLES /// Lookup an T::AccountIndex to get an Id, if there's one there. pub fn lookup_index(index: T::AccountIndex) -> Option { let enum_set_size = Self::enum_set_size(); let set = Self::enum_set(index / enum_set_size); let i: usize = (index % enum_set_size).try_into().ok()?; set.get(i).cloned() } /// `true` if the account `index` is ready for reclaim. pub fn can_reclaim(try_index: T::AccountIndex) -> bool { let enum_set_size = Self::enum_set_size(); let try_set = Self::enum_set(try_index / enum_set_size); let maybe_usize: Result = (try_index % enum_set_size).try_into(); if let Ok(i) = maybe_usize { i < try_set.len() && T::IsDeadAccount::is_dead_account(&try_set[i]) } else { false } } /// Lookup an address to get an Id, if there's one there. pub fn lookup_address( a: address::Address ) -> Option { match a { address::Address::Id(i) => Some(i), address::Address::Index(i) => Self::lookup_index(i), } } // PUBLIC MUTABLES (DANGEROUS) fn enum_set_size() -> T::AccountIndex { ENUM_SET_SIZE.into() } } impl OnNewAccount for Module { // Implementation of the config type managing the creation of new accounts. // See Balances module for a concrete example. // // # // - Independent of the arguments. // - Given the correct value of `Self::next_enum_set`, it always has a limited // number of reads and writes and no complex computation. // // As for storage, calling this function with _non-dead-indices_ will linearly grow the length of // of `Self::enum_set`. Appropriate economic incentives should exist to make callers of this // function provide a `who` argument that reclaims a dead account. // // At the time of this writing, only the Balances module calls this function upon creation // of new accounts. // # fn on_new_account(who: &T::AccountId) { let enum_set_size = Self::enum_set_size(); let next_set_index = Self::next_enum_set(); if let Some(try_index) = T::ResolveHint::resolve_hint(who) { // then check to see if this account id identifies a dead account index. let set_index = try_index / enum_set_size; let mut try_set = Self::enum_set(set_index); if let Ok(item_index) = (try_index % enum_set_size).try_into() { if item_index < try_set.len() { if T::IsDeadAccount::is_dead_account(&try_set[item_index]) { // yup - this index refers to a dead account. can be reused. try_set[item_index] = who.clone(); >::insert(set_index, try_set); return } } } } // insert normally as a back up let mut set_index = next_set_index; // defensive only: this loop should never iterate since we keep NextEnumSet up to date // later. let mut set = loop { let set = Self::enum_set(set_index); if set.len() < ENUM_SET_SIZE as usize { break set; } set_index += One::one(); }; let index = set_index * enum_set_size + T::AccountIndex::from(set.len() as u32); // update set. set.push(who.clone()); // keep NextEnumSet up to date if set.len() == ENUM_SET_SIZE as usize { >::put(set_index + One::one()); } // write set. >::insert(set_index, set); Self::deposit_event(RawEvent::NewAccountIndex(who.clone(), index)); } } impl StaticLookup for Module { type Source = address::Address; type Target = T::AccountId; fn lookup(a: Self::Source) -> Result { Self::lookup_address(a).ok_or(LookupError) } fn unlookup(a: Self::Target) -> Self::Source { address::Address::Id(a) } }