Skip to content
Snippets Groups Projects
lib.rs 7.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bryan Chen's avatar
    Bryan Chen committed
    #![cfg_attr(not(feature = "std"), no_std)]
    
    
    use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, traits::Get, Parameter};
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use rstd::convert::{TryFrom, TryInto};
    
    use sp_runtime::{
    
    	traits::{CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SimpleArithmetic, StaticLookup, Zero},
    
    	DispatchResult,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    // FIXME: `pallet/frame-` prefix should be used for all pallet modules, but currently `frame_system`
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    // would cause compiling error in `decl_module!` and `construct_runtime!`
    // #3295 https://github.com/paritytech/substrate/issues/3295
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use frame_system::{self as system, ensure_signed};
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    #[cfg(feature = "std")]
    use rstd::collections::btree_map::BTreeMap;
    
    
    use orml_traits::{
    
    	arithmetic::{self, Signed},
    
    	MultiCurrency, MultiCurrencyExtended, OnDustRemoval,
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    mod mock;
    mod tests;
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    pub trait Trait: frame_system::Trait {
    	type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	type Balance: Parameter + Member + SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize;
    
    	type Amount: Signed
    		+ TryInto<Self::Balance>
    		+ TryFrom<Self::Balance>
    		+ Parameter
    		+ Member
    		+ arithmetic::SimpleArithmetic
    		+ Default
    		+ Copy
    		+ MaybeSerializeDeserialize;
    
    	type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord;
    
    	type ExistentialDeposit: Get<Self::Balance>;
    	type DustRemoval: OnDustRemoval<Self::Balance>;
    
    Bryan Chen's avatar
    Bryan Chen committed
    }
    
    decl_storage! {
    	trait Store for Module<T: Trait> as Tokens {
    
    		/// The total issuance of a token type.
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig<T>| {
    
    			config
    				.endowed_accounts
    				.iter()
    				.map(|(_, currency_id, initial_balance)| (currency_id, initial_balance))
    				.fold(BTreeMap::<T::CurrencyId, T::Balance>::new(), |mut acc, (currency_id, initial_balance)| {
    					if let Some(issuance) = acc.get_mut(currency_id) {
    						*issuance = issuance.checked_add(initial_balance).expect("total issuance cannot overflow when building genesis");
    					} else {
    						acc.insert(*currency_id, *initial_balance);
    					}
    					acc
    				})
    				.into_iter()
    				.collect::<Vec<_>>()
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		}): map hasher(blake2_256) T::CurrencyId => T::Balance;
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		/// The balance of a token type under an account.
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		pub Balance get(fn balance): double_map hasher(blake2_256) T::CurrencyId, hasher(blake2_256) T::AccountId => T::Balance;
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	}
    	add_extra_genesis {
    
    		config(endowed_accounts): Vec<(T::AccountId, T::CurrencyId, T::Balance)>;
    
    
    		build(|config: &GenesisConfig<T>| {
    
    			config.endowed_accounts.iter().for_each(|(account_id, currency_id, initial_balance)| {
    				<Balance<T>>::insert(currency_id, account_id, initial_balance);
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    }
    
    decl_event!(
    	pub enum Event<T> where
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		<T as frame_system::Trait>::AccountId,
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		<T as Trait>::CurrencyId,
    		<T as Trait>::Balance
    
    Bryan Chen's avatar
    Bryan Chen committed
    	{
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		/// Token transfer success (currency_id, from, to, amount)
    		Transferred(CurrencyId, AccountId, AccountId, Balance),
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    );
    
    decl_module! {
    	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    
    		type Error = Error<T>;
    
    
    Bryan Chen's avatar
    Bryan Chen committed
    		fn deposit_event() = default;
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		/// Transfer some balance to another account.
    		pub fn transfer(
    			origin,
    			dest: <T::Lookup as StaticLookup>::Source,
    
    			currency_id: T::CurrencyId,
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    			#[compact] amount: T::Balance,
    		) {
    			let from = ensure_signed(origin)?;
    			let to = T::Lookup::lookup(dest)?;
    			<Self as MultiCurrency<_>>::transfer(currency_id, &from, &to, amount)?;
    
    			Self::deposit_event(RawEvent::Transferred(currency_id, from, to, amount));
    		}
    
    
    		/// Transfer all remaining balance to the given account.
    		pub fn transfer_all(
    			origin,
    			dest: <T::Lookup as StaticLookup>::Source,
    			currency_id: T::CurrencyId,
    		) {
    			let from = ensure_signed(origin)?;
    			let to = T::Lookup::lookup(dest)?;
    			let balance = Self::balance(currency_id, &from);
    			<Self as MultiCurrency<T::AccountId>>::transfer(currency_id, &from, &to, balance)?;
    
    			Self::deposit_event(RawEvent::Transferred(currency_id, from, to, balance));
    		}
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    }
    
    
    decl_error! {
    	/// Error for token module.
    
    	pub enum Error for Module<T: Trait> {
    
    		BalanceTooLow,
    		TotalIssuanceOverflow,
    
    		AmountIntoBalanceFailed,
    
    		ExistentialDeposit,
    
    impl<T: Trait> Module<T> {
    	/// Set balance of `who` to a new value, meanwhile enforce existential rule.
    	///
    	/// Note this will not maintain total issuance, and the caller is expected to do it.
    	fn set_balance(currency_id: T::CurrencyId, who: &T::AccountId, balance: T::Balance) {
    		if balance < T::ExistentialDeposit::get() {
    			<Balance<T>>::remove(currency_id, who);
    			T::DustRemoval::on_dust_removal(balance);
    		} else {
    			<Balance<T>>::insert(currency_id, who, balance);
    		}
    	}
    }
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    impl<T: Trait> MultiCurrency<T::AccountId> for Module<T> {
    	type CurrencyId = T::CurrencyId;
    
    	fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance {
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		<TotalIssuance<T>>::get(currency_id)
    	}
    
    	fn balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance {
    		<Balance<T>>::get(currency_id, who)
    	}
    
    
    	fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
    
    		if Self::balance(currency_id, who).checked_sub(&amount).is_some() {
    			Ok(())
    		} else {
    
    			Err(Error::<T>::BalanceTooLow.into())
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	fn transfer(
    		currency_id: Self::CurrencyId,
    		from: &T::AccountId,
    		to: &T::AccountId,
    		amount: Self::Balance,
    
    	) -> DispatchResult {
    
    		let from_balance = Self::balance(currency_id, from);
    		ensure!(from_balance >= amount, Error::<T>::BalanceTooLow);
    
    		let to_balance = Self::balance(currency_id, to);
    		if to_balance.is_zero() && amount < T::ExistentialDeposit::get() {
    			return Err(Error::<T>::ExistentialDeposit.into());
    		}
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    
    		if from != to {
    
    			Self::set_balance(currency_id, from, from_balance - amount);
    			Self::set_balance(currency_id, to, to_balance + amount);
    
    	fn deposit(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		ensure!(
    
    			Self::total_issuance(currency_id).checked_add(&amount).is_some(),
    
    			Error::<T>::TotalIssuanceOverflow,
    
    		let balance = Self::balance(currency_id, who);
    		// Nothing happens if deposition doesn't meet existential deposit rule,
    		// consistent behavior with pallet-balances.
    		if balance.is_zero() && amount < T::ExistentialDeposit::get() {
    			return Ok(());
    		}
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		<TotalIssuance<T>>::mutate(currency_id, |v| *v += amount);
    
    
    		Self::set_balance(currency_id, who, balance + amount);
    
    	fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult {
    
    		let balance = Self::balance(currency_id, who);
    		ensure!(balance.checked_sub(&amount).is_some(), Error::<T>::BalanceTooLow);
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    
    		<TotalIssuance<T>>::mutate(currency_id, |v| *v -= amount);
    
    		Self::set_balance(currency_id, who, balance - amount);
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    
    		Ok(())
    	}
    
    	fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance {
    
    		let balance = Self::balance(currency_id, who);
    		let slashed_amount = balance.min(amount);
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		<TotalIssuance<T>>::mutate(currency_id, |v| *v -= slashed_amount);
    
    		Self::set_balance(currency_id, who, balance - slashed_amount);
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    		amount - slashed_amount
    
    
    impl<T: Trait> MultiCurrencyExtended<T::AccountId> for Module<T> {
    	type Amount = T::Amount;
    
    
    	fn update_balance(currency_id: Self::CurrencyId, who: &T::AccountId, by_amount: Self::Amount) -> DispatchResult {
    
    			TryInto::<Self::Balance>::try_into(by_amount.abs()).map_err(|_| Error::<T>::AmountIntoBalanceFailed)?;
    
    		if by_amount.is_positive() {
    			Self::deposit(currency_id, who, by_balance)
    		} else {
    			Self::withdraw(currency_id, who, by_balance)
    		}
    	}
    }