mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 21:57:55 +00:00
Composite accounts (#4820)
* Basic account composition. * Add try_mutate_exists * De-duplicate * Refactor away the UpdateBalanceOutcome * Expunge final UpdateBalanceOutcome refs * Refactor transfer * Refactor reservable currency stuff. * Test with the alternative setup. * Fixes * Test with both setups. * Fixes * Fix * Fix macros * Make indices opt-in * Remove CreationFee, and make indices opt-in. * Fix construct_runtime * Fix last few bits * Fix tests * Update trait impls * Don't hardcode the system event * Make tests build and fix some stuff. * Pointlessly bump runtime version * Fix benchmark * Another fix * Whitespace * Make indices module economically safe * Migrations for indices. * Fix * Whilespace * Trim defunct migrations * Remove unused storage item * More contains_key fixes * Docs. * Bump runtime * Remove unneeded code * Fix test * Fix test * Update frame/balances/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Fix ED logic * Repatriate reserved logic * Typo * Fix typo * Update frame/system/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/system/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Last few fixes * Another fix * Build fix Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Jaco Greeff <jacogr@gmail.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
+181
-142
@@ -19,41 +19,24 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::{prelude::*, marker::PhantomData, convert::TryInto};
|
||||
use codec::{Encode, Codec};
|
||||
use frame_support::{Parameter, decl_module, decl_event, decl_storage};
|
||||
use sp_runtime::traits::{One, AtLeast32Bit, StaticLookup, Member, LookupError};
|
||||
use frame_system::{IsDeadAccount, OnNewAccount};
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use codec::Codec;
|
||||
use sp_runtime::traits::{
|
||||
StaticLookup, Member, LookupError, Zero, One, BlakeTwo256, Hash, Saturating, AtLeast32Bit
|
||||
};
|
||||
use frame_support::{Parameter, decl_module, decl_error, decl_event, decl_storage, ensure};
|
||||
use frame_support::dispatch::DispatchResult;
|
||||
use frame_support::traits::{Currency, ReservableCurrency, Get, BalanceStatus::Reserved};
|
||||
use frame_support::storage::migration::take_storage_value;
|
||||
use frame_system::{ensure_signed, ensure_root};
|
||||
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<T> = RawAddress<<T as frame_system::Trait>::AccountId, <T as Trait>::AccountIndex>;
|
||||
|
||||
/// Turn an Id into an Index, or None for the purpose of getting
|
||||
/// a hint at a possibly desired index.
|
||||
pub trait ResolveHint<AccountId, AccountIndex> {
|
||||
/// 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<AccountIndex>;
|
||||
}
|
||||
|
||||
/// Simple encode-based resolve hint implementation.
|
||||
pub struct SimpleResolveHint<AccountId, AccountIndex>(PhantomData<(AccountId, AccountIndex)>);
|
||||
impl<AccountId: Encode, AccountIndex: From<u32>>
|
||||
ResolveHint<AccountId, AccountIndex> for SimpleResolveHint<AccountId, AccountIndex>
|
||||
{
|
||||
fn resolve_hint(who: &AccountId) -> Option<AccountIndex> {
|
||||
Some(AccountIndex::from(who.using_encoded(|e| e[0] as u32 + e[1] as u32 * 256)))
|
||||
}
|
||||
}
|
||||
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
|
||||
|
||||
/// The module's config trait.
|
||||
pub trait Trait: frame_system::Trait {
|
||||
@@ -61,19 +44,28 @@ pub trait Trait: frame_system::Trait {
|
||||
/// can hold.
|
||||
type AccountIndex: Parameter + Member + Codec + Default + AtLeast32Bit + Copy;
|
||||
|
||||
/// Whether an account is dead or not.
|
||||
type IsDeadAccount: IsDeadAccount<Self::AccountId>;
|
||||
/// The currency trait.
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// How to turn an id into an index.
|
||||
type ResolveHint: ResolveHint<Self::AccountId, Self::AccountIndex>;
|
||||
/// The deposit needed for reserving an index.
|
||||
type Deposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin, system = frame_system {
|
||||
fn deposit_event() = default;
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Indices {
|
||||
/// The lookup from index to account.
|
||||
pub Accounts build(|config: &GenesisConfig<T>|
|
||||
config.indices.iter()
|
||||
.cloned()
|
||||
.map(|(a, b)| (a, (b, Zero::zero())))
|
||||
.collect::<Vec<_>>()
|
||||
): map hasher(blake2_128_concat) T::AccountIndex => Option<(T::AccountId, BalanceOf<T>)>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(indices): Vec<(T::AccountIndex, T::AccountId)>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,36 +74,146 @@ decl_event!(
|
||||
<T as frame_system::Trait>::AccountId,
|
||||
<T as Trait>::AccountIndex
|
||||
{
|
||||
/// A new account index was assigned.
|
||||
///
|
||||
/// This event is not triggered when an existing index is reassigned
|
||||
/// to another `AccountId`.
|
||||
NewAccountIndex(AccountId, AccountIndex),
|
||||
/// A account index was assigned.
|
||||
IndexAssigned(AccountId, AccountIndex),
|
||||
/// A account index has been freed up (unassigned).
|
||||
IndexFreed(AccountIndex),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Indices {
|
||||
/// The next free enumeration set.
|
||||
pub NextEnumSet get(fn next_enum_set) build(|config: &GenesisConfig<T>| {
|
||||
(config.ids.len() as u32 / ENUM_SET_SIZE).into()
|
||||
}): T::AccountIndex;
|
||||
|
||||
/// The enumeration sets.
|
||||
pub EnumSet get(fn enum_set) build(|config: &GenesisConfig<T>| {
|
||||
(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::<Vec<_>>()
|
||||
}): map hasher(blake2_256) T::AccountIndex => Vec<T::AccountId>;
|
||||
decl_error! {
|
||||
pub enum Error for Module<T: Trait> {
|
||||
/// The index was not already assigned.
|
||||
NotAssigned,
|
||||
/// The index is assigned to another account.
|
||||
NotOwner,
|
||||
/// The index was not available.
|
||||
InUse,
|
||||
/// The source and destination accounts are identical.
|
||||
NotTransfer,
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(ids): Vec<T::AccountId>;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin, system = frame_system {
|
||||
fn deposit_event() = default;
|
||||
|
||||
fn on_initialize() {
|
||||
Self::migrations();
|
||||
}
|
||||
|
||||
/// Assign an previously unassigned index.
|
||||
///
|
||||
/// Payment: `Deposit` is reserved from the sender account.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// - `index`: the index to be claimed. This must not be in use.
|
||||
///
|
||||
/// Emits `IndexAssigned` if successful.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - `O(1)`.
|
||||
/// - One storage mutation (codec `O(1)`).
|
||||
/// - One reserve operation.
|
||||
/// - One event.
|
||||
/// # </weight>
|
||||
fn claim(origin, index: T::AccountIndex) {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
Accounts::<T>::try_mutate(index, |maybe_value| {
|
||||
ensure!(maybe_value.is_none(), Error::<T>::InUse);
|
||||
*maybe_value = Some((who.clone(), T::Deposit::get()));
|
||||
T::Currency::reserve(&who, T::Deposit::get())
|
||||
})?;
|
||||
Self::deposit_event(RawEvent::IndexAssigned(who, index));
|
||||
}
|
||||
|
||||
/// Assign an index already owned by the sender to another account. The balance reservation
|
||||
/// is effectively transfered to the new account.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// - `index`: the index to be re-assigned. This must be owned by the sender.
|
||||
/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
|
||||
///
|
||||
/// Emits `IndexAssigned` if successful.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - `O(1)`.
|
||||
/// - One storage mutation (codec `O(1)`).
|
||||
/// - One transfer operation.
|
||||
/// - One event.
|
||||
/// # </weight>
|
||||
fn transfer(origin, new: T::AccountId, index: T::AccountIndex) {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(who != new, Error::<T>::NotTransfer);
|
||||
|
||||
Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
|
||||
let (account, amount) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
|
||||
ensure!(&account == &who, Error::<T>::NotOwner);
|
||||
let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?;
|
||||
*maybe_value = Some((new.clone(), amount.saturating_sub(lost)));
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(RawEvent::IndexAssigned(new, index));
|
||||
}
|
||||
|
||||
/// Free up an index owned by the sender.
|
||||
///
|
||||
/// Payment: Any previous deposit placed for the index is unreserved in the sender account.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and the sender must own the index.
|
||||
///
|
||||
/// - `index`: the index to be freed. This must be owned by the sender.
|
||||
///
|
||||
/// Emits `IndexFreed` if successful.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - `O(1)`.
|
||||
/// - One storage mutation (codec `O(1)`).
|
||||
/// - One reserve operation.
|
||||
/// - One event.
|
||||
/// # </weight>
|
||||
fn free(origin, index: T::AccountIndex) {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
|
||||
let (account, amount) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
|
||||
ensure!(&account == &who, Error::<T>::NotOwner);
|
||||
T::Currency::unreserve(&who, amount);
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(RawEvent::IndexFreed(index));
|
||||
}
|
||||
|
||||
/// Force an index to an account. This doesn't require a deposit. If the index is already
|
||||
/// held, then any deposit is reimbursed to its current owner.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Root_.
|
||||
///
|
||||
/// - `index`: the index to be (re-)assigned.
|
||||
/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
|
||||
///
|
||||
/// Emits `IndexAssigned` if successful.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - `O(1)`.
|
||||
/// - One storage mutation (codec `O(1)`).
|
||||
/// - Up to one reserve operation.
|
||||
/// - One event.
|
||||
/// # </weight>
|
||||
fn force_transfer(origin, new: T::AccountId, index: T::AccountIndex) {
|
||||
ensure_root(origin)?;
|
||||
|
||||
Accounts::<T>::mutate(index, |maybe_value| {
|
||||
if let Some((account, amount)) = maybe_value.take() {
|
||||
T::Currency::unreserve(&account, amount);
|
||||
}
|
||||
*maybe_value = Some((new.clone(), Zero::zero()));
|
||||
});
|
||||
Self::deposit_event(RawEvent::IndexAssigned(new, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,22 +222,7 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
/// Lookup an T::AccountIndex to get an Id, if there's one there.
|
||||
pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
|
||||
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<usize, _> = (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
|
||||
}
|
||||
Accounts::<T>::get(index).map(|x| x.0)
|
||||
}
|
||||
|
||||
/// Lookup an address to get an Id, if there's one there.
|
||||
@@ -148,76 +235,28 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC MUTABLES (DANGEROUS)
|
||||
/// Do any migrations.
|
||||
fn migrations() {
|
||||
if let Some(set_count) = take_storage_value::<T::AccountIndex>(b"Indices", b"NextEnumSet", b"") {
|
||||
// migrations need doing.
|
||||
let set_size: T::AccountIndex = 64.into();
|
||||
|
||||
fn enum_set_size() -> T::AccountIndex {
|
||||
ENUM_SET_SIZE.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnNewAccount<T::AccountId> for Module<T> {
|
||||
// Implementation of the config type managing the creation of new accounts.
|
||||
// See Balances module for a concrete example.
|
||||
//
|
||||
// # <weight>
|
||||
// - 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.
|
||||
// # </weight>
|
||||
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();
|
||||
<EnumSet<T>>::insert(set_index, try_set);
|
||||
|
||||
return
|
||||
let mut set_index: T::AccountIndex = Zero::zero();
|
||||
while set_index < set_count {
|
||||
let maybe_accounts = take_storage_value::<Vec<T::AccountId>>(b"Indices", b"EnumSet", BlakeTwo256::hash_of(&set_index).as_ref());
|
||||
if let Some(accounts) = maybe_accounts {
|
||||
for (item_index, target) in accounts.into_iter().enumerate() {
|
||||
if target != T::AccountId::default() && !T::Currency::total_balance(&target).is_zero() {
|
||||
let index = set_index * set_size + T::AccountIndex::from(item_index as u32);
|
||||
Accounts::<T>::insert(index, (target, BalanceOf::<T>::zero()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
set_index += One::one();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
<NextEnumSet<T>>::put(set_index + One::one());
|
||||
}
|
||||
|
||||
// write set.
|
||||
<EnumSet<T>>::insert(set_index, set);
|
||||
|
||||
Self::deposit_event(RawEvent::NewAccountIndex(who.clone(), index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,51 +18,29 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::{cell::RefCell, collections::HashSet};
|
||||
use sp_runtime::testing::Header;
|
||||
use sp_runtime::Perbill;
|
||||
use sp_core::H256;
|
||||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
|
||||
use crate::{GenesisConfig, Module, Trait, IsDeadAccount, OnNewAccount, ResolveHint};
|
||||
use frame_support::{impl_outer_origin, impl_outer_event, parameter_types, weights::Weight};
|
||||
use crate::{self as indices, Module, Trait};
|
||||
use frame_system as system;
|
||||
use pallet_balances as balances;
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Runtime where system = frame_system {}
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static ALIVE: RefCell<HashSet<u64>> = Default::default();
|
||||
}
|
||||
|
||||
pub fn make_account(who: u64) {
|
||||
ALIVE.with(|a| a.borrow_mut().insert(who));
|
||||
Indices::on_new_account(&who);
|
||||
}
|
||||
|
||||
pub fn kill_account(who: u64) {
|
||||
ALIVE.with(|a| a.borrow_mut().remove(&who));
|
||||
}
|
||||
|
||||
pub struct TestIsDeadAccount {}
|
||||
impl IsDeadAccount<u64> for TestIsDeadAccount {
|
||||
fn is_dead_account(who: &u64) -> bool {
|
||||
!ALIVE.with(|a| a.borrow_mut().contains(who))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestResolveHint;
|
||||
impl ResolveHint<u64, u64> for TestResolveHint {
|
||||
fn resolve_hint(who: &u64) -> Option<u64> {
|
||||
if *who < 256 {
|
||||
None
|
||||
} else {
|
||||
Some(*who - 256)
|
||||
}
|
||||
impl_outer_event!{
|
||||
pub enum MetaEvent for Test {
|
||||
system<T>,
|
||||
balances<T>,
|
||||
indices<T>,
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Runtime;
|
||||
pub struct Test;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
@@ -70,46 +48,59 @@ parameter_types! {
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Trait for Runtime {
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = Indices;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Event = MetaEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
}
|
||||
|
||||
impl Trait for Runtime {
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
}
|
||||
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
type DustRemoval = ();
|
||||
type Event = MetaEvent;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const Deposit: u64 = 1;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type AccountIndex = u64;
|
||||
type IsDeadAccount = TestIsDeadAccount;
|
||||
type ResolveHint = TestResolveHint;
|
||||
type Event = ();
|
||||
type Currency = Balances;
|
||||
type Deposit = Deposit;
|
||||
type Event = MetaEvent;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
{
|
||||
ALIVE.with(|a| {
|
||||
let mut h = a.borrow_mut();
|
||||
h.clear();
|
||||
for i in 1..5 { h.insert(i); }
|
||||
});
|
||||
}
|
||||
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
|
||||
GenesisConfig::<Runtime> {
|
||||
ids: vec![1, 2, 3, 4]
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test>{
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub type Indices = Module<Runtime>;
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Balances = pallet_balances::Module<Test>;
|
||||
pub type Indices = Module<Test>;
|
||||
|
||||
@@ -19,49 +19,85 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate::mock::{Indices, new_test_ext, make_account, kill_account, TestIsDeadAccount};
|
||||
use super::mock::*;
|
||||
use frame_support::{assert_ok, assert_noop};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
|
||||
#[test]
|
||||
fn claiming_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Indices::claim(Some(0).into(), 0), BalancesError::<Test, _>::InsufficientBalance);
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_noop!(Indices::claim(Some(2).into(), 0), Error::<Test>::InUse);
|
||||
assert_eq!(Balances::reserved_balance(1), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freeing_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_ok!(Indices::claim(Some(2).into(), 1));
|
||||
assert_noop!(Indices::free(Some(0).into(), 0), Error::<Test>::NotOwner);
|
||||
assert_noop!(Indices::free(Some(1).into(), 1), Error::<Test>::NotOwner);
|
||||
assert_noop!(Indices::free(Some(1).into(), 2), Error::<Test>::NotAssigned);
|
||||
assert_ok!(Indices::free(Some(1).into(), 0));
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_noop!(Indices::free(Some(1).into(), 0), Error::<Test>::NotAssigned);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing_lookup_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_ok!(Indices::claim(Some(2).into(), 1));
|
||||
assert_eq!(Indices::lookup_index(0), Some(1));
|
||||
assert_eq!(Indices::lookup_index(1), Some(2));
|
||||
assert_eq!(Indices::lookup_index(2), Some(3));
|
||||
assert_eq!(Indices::lookup_index(3), Some(4));
|
||||
assert_eq!(Indices::lookup_index(4), None);
|
||||
assert_eq!(Indices::lookup_index(2), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_indexing_on_new_accounts_should_work() {
|
||||
fn reclaim_index_on_accounts_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Indices::lookup_index(4), None);
|
||||
make_account(5);
|
||||
assert_eq!(Indices::lookup_index(4), Some(5));
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_ok!(Indices::free(Some(1).into(), 0));
|
||||
assert_ok!(Indices::claim(Some(2).into(), 0));
|
||||
assert_eq!(Indices::lookup_index(0), Some(2));
|
||||
assert_eq!(Balances::reserved_balance(2), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reclaim_indexing_on_new_accounts_should_work() {
|
||||
fn transfer_index_on_accounts_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Indices::lookup_index(1), Some(2));
|
||||
assert_eq!(Indices::lookup_index(4), None);
|
||||
|
||||
kill_account(2); // index 1 no longer locked to id 2
|
||||
|
||||
make_account(1 + 256); // id 257 takes index 1.
|
||||
assert_eq!(Indices::lookup_index(1), Some(257));
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_noop!(Indices::transfer(Some(1).into(), 2, 1), Error::<Test>::NotAssigned);
|
||||
assert_noop!(Indices::transfer(Some(2).into(), 3, 0), Error::<Test>::NotOwner);
|
||||
assert_ok!(Indices::transfer(Some(1).into(), 3, 0));
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(3), 1);
|
||||
assert_eq!(Indices::lookup_index(0), Some(3));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alive_account_should_prevent_reclaim() {
|
||||
fn force_transfer_index_on_preowned_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(!TestIsDeadAccount::is_dead_account(&2));
|
||||
assert_eq!(Indices::lookup_index(1), Some(2));
|
||||
assert_eq!(Indices::lookup_index(4), None);
|
||||
|
||||
make_account(1 + 256); // id 257 takes index 1.
|
||||
assert_eq!(Indices::lookup_index(4), Some(257));
|
||||
assert_ok!(Indices::claim(Some(1).into(), 0));
|
||||
assert_ok!(Indices::force_transfer(Origin::ROOT, 3, 0));
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
assert_eq!(Indices::lookup_index(0), Some(3));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_transfer_index_on_free_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Indices::force_transfer(Origin::ROOT, 3, 0));
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
assert_eq!(Indices::lookup_index(0), Some(3));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user