#![cfg_attr(not(feature = "std"), no_std)]
use tetcore_std::prelude::*;
use tp_runtime::{
traits::{StaticLookup, Zero}
};
use fabric_support::{
decl_module, decl_event, decl_storage, ensure, decl_error,
traits::{Currency, EnsureOrigin, ReservableCurrency, OnUnbalanced, Get},
};
use fabric_system::ensure_signed;
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as fabric_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as fabric_system::Config>::AccountId>>::NegativeImbalance;
pub trait Config: fabric_system::Config {
type Event: From<Event<Self>> + Into<<Self as fabric_system::Config>::Event>;
type Currency: ReservableCurrency<Self::AccountId>;
type ReservationFee: Get<BalanceOf<Self>>;
type Slashed: OnUnbalanced<NegativeImbalanceOf<Self>>;
type ForceOrigin: EnsureOrigin<Self::Origin>;
type MinLength: Get<usize>;
type MaxLength: Get<usize>;
}
decl_storage! {
trait Store for Module<T: Config> as Nicks {
NameOf: map hasher(twox_64_concat) T::AccountId => Option<(Vec<u8>, BalanceOf<T>)>;
}
}
decl_event!(
pub enum Event<T> where AccountId = <T as fabric_system::Config>::AccountId, Balance = BalanceOf<T> {
NameSet(AccountId),
NameForced(AccountId),
NameChanged(AccountId),
NameCleared(AccountId, Balance),
NameKilled(AccountId, Balance),
}
);
decl_error! {
pub enum Error for Module<T: Config> {
TooShort,
TooLong,
Unnamed,
}
}
decl_module! {
pub struct Module<T: Config> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
const ReservationFee: BalanceOf<T> = T::ReservationFee::get();
const MinLength: u32 = T::MinLength::get() as u32;
const MaxLength: u32 = T::MaxLength::get() as u32;
#[weight = 50_000_000]
fn set_name(origin, name: Vec<u8>) {
let sender = ensure_signed(origin)?;
ensure!(name.len() >= T::MinLength::get(), Error::<T>::TooShort);
ensure!(name.len() <= T::MaxLength::get(), Error::<T>::TooLong);
let deposit = if let Some((_, deposit)) = <NameOf<T>>::get(&sender) {
Self::deposit_event(RawEvent::NameChanged(sender.clone()));
deposit
} else {
let deposit = T::ReservationFee::get();
T::Currency::reserve(&sender, deposit.clone())?;
Self::deposit_event(RawEvent::NameSet(sender.clone()));
deposit
};
<NameOf<T>>::insert(&sender, (name, deposit));
}
#[weight = 70_000_000]
fn clear_name(origin) {
let sender = ensure_signed(origin)?;
let deposit = <NameOf<T>>::take(&sender).ok_or(Error::<T>::Unnamed)?.1;
let _ = T::Currency::unreserve(&sender, deposit.clone());
Self::deposit_event(RawEvent::NameCleared(sender, deposit));
}
#[weight = 70_000_000]
fn kill_name(origin, target: <T::Lookup as StaticLookup>::Source) {
T::ForceOrigin::ensure_origin(origin)?;
let target = T::Lookup::lookup(target)?;
let deposit = <NameOf<T>>::take(&target).ok_or(Error::<T>::Unnamed)?.1;
T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit.clone()).0);
Self::deposit_event(RawEvent::NameKilled(target, deposit));
}
#[weight = 70_000_000]
fn force_name(origin, target: <T::Lookup as StaticLookup>::Source, name: Vec<u8>) {
T::ForceOrigin::ensure_origin(origin)?;
let target = T::Lookup::lookup(target)?;
let deposit = <NameOf<T>>::get(&target).map(|x| x.1).unwrap_or_else(Zero::zero);
<NameOf<T>>::insert(&target, (name, deposit));
Self::deposit_event(RawEvent::NameForced(target));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fabric_support::{
assert_ok, assert_noop, impl_outer_origin, parameter_types,
ord_parameter_types
};
use tet_core::H256;
use fabric_system::EnsureSignedBy;
use tp_runtime::{
testing::Header, traits::{BlakeTwo256, IdentityLookup, BadOrigin},
};
impl_outer_origin! {
pub enum Origin for Test where system = fabric_system {}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: fabric_system::limits::BlockWeights =
fabric_system::limits::BlockWeights::simple_max(1024);
}
impl fabric_system::Config for Test {
type BaseCallFilter = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Call = ();
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type BlockHashCount = BlockHashCount;
type Version = ();
type NobleInfo = ();
type AccountData = noble_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl noble_balances::Config for Test {
type MaxLocks = ();
type Balance = u64;
type Event = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
parameter_types! {
pub const ReservationFee: u64 = 2;
pub const MinLength: usize = 3;
pub const MaxLength: usize = 16;
}
ord_parameter_types! {
pub const One: u64 = 1;
}
impl Config for Test {
type Event = ();
type Currency = Balances;
type ReservationFee = ReservationFee;
type Slashed = ();
type ForceOrigin = EnsureSignedBy<One, u64>;
type MinLength = MinLength;
type MaxLength = MaxLength;
}
type System = fabric_system::Module<Test>;
type Balances = noble_balances::Module<Test>;
type Nicks = Module<Test>;
fn new_test_ext() -> tet_io::TestExternalities {
let mut t = fabric_system::GenesisConfig::default().build_storage::<Test>().unwrap();
noble_balances::GenesisConfig::<Test> {
balances: vec![
(1, 10),
(2, 10),
],
}.assimilate_storage(&mut t).unwrap();
t.into()
}
#[test]
fn kill_name_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nicks::set_name(Origin::signed(2), b"Dave".to_vec()));
assert_eq!(Balances::total_balance(&2), 10);
assert_ok!(Nicks::kill_name(Origin::signed(1), 2));
assert_eq!(Balances::total_balance(&2), 8);
assert_eq!(<NameOf<Test>>::get(2), None);
});
}
#[test]
fn force_name_should_work() {
new_test_ext().execute_with(|| {
assert_noop!(
Nicks::set_name(Origin::signed(2), b"Dr. David Brubeck, III".to_vec()),
Error::<Test>::TooLong,
);
assert_ok!(Nicks::set_name(Origin::signed(2), b"Dave".to_vec()));
assert_eq!(Balances::reserved_balance(2), 2);
assert_ok!(Nicks::force_name(Origin::signed(1), 2, b"Dr. David Brubeck, III".to_vec()));
assert_eq!(Balances::reserved_balance(2), 2);
assert_eq!(<NameOf<Test>>::get(2).unwrap(), (b"Dr. David Brubeck, III".to_vec(), 2));
});
}
#[test]
fn normal_operation_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nicks::set_name(Origin::signed(1), b"Gav".to_vec()));
assert_eq!(Balances::reserved_balance(1), 2);
assert_eq!(Balances::free_balance(1), 8);
assert_eq!(<NameOf<Test>>::get(1).unwrap().0, b"Gav".to_vec());
assert_ok!(Nicks::set_name(Origin::signed(1), b"Gavin".to_vec()));
assert_eq!(Balances::reserved_balance(1), 2);
assert_eq!(Balances::free_balance(1), 8);
assert_eq!(<NameOf<Test>>::get(1).unwrap().0, b"Gavin".to_vec());
assert_ok!(Nicks::clear_name(Origin::signed(1)));
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::free_balance(1), 10);
});
}
#[test]
fn error_catching_should_work() {
new_test_ext().execute_with(|| {
assert_noop!(Nicks::clear_name(Origin::signed(1)), Error::<Test>::Unnamed);
assert_noop!(
Nicks::set_name(Origin::signed(3), b"Dave".to_vec()),
noble_balances::Error::<Test, _>::InsufficientBalance
);
assert_noop!(Nicks::set_name(Origin::signed(1), b"Ga".to_vec()), Error::<Test>::TooShort);
assert_noop!(
Nicks::set_name(Origin::signed(1), b"Gavin James Wood, Esquire".to_vec()),
Error::<Test>::TooLong
);
assert_ok!(Nicks::set_name(Origin::signed(1), b"Dave".to_vec()));
assert_noop!(Nicks::kill_name(Origin::signed(2), 1), BadOrigin);
assert_noop!(Nicks::force_name(Origin::signed(2), 1, b"Whatever".to_vec()), BadOrigin);
});
}
}