Skip to content
Snippets Groups Projects
lib.rs 5.63 KiB
Newer Older
Bryan Chen's avatar
Bryan Chen committed
#![cfg_attr(not(feature = "std"), no_std)]

Xiliang Chen's avatar
Xiliang Chen committed
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
use rstd::{
	convert::{TryFrom, TryInto},
	result,
};
Xiliang Chen's avatar
Xiliang Chen committed
use sp_runtime::traits::{CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SimpleArithmetic, StaticLookup};
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

use orml_traits::{
	arithmetic::{self, Signed},
	MultiCurrency, MultiCurrencyExtended,
};
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;
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>| {
			let issuance = config.initial_balance * (config.endowed_accounts.len() as u32).into();
			config.tokens.iter().map(|id| (id.clone(), issuance)).collect::<Vec<_>>()
		}): map 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.
		pub Balance get(fn balance): double_map T::CurrencyId, blake2_256(T::AccountId) => T::Balance;
	}
	add_extra_genesis {
		config(tokens): Vec<T::CurrencyId>;
		config(initial_balance): T::Balance;
		config(endowed_accounts): Vec<T::AccountId>;

		build(|config: &GenesisConfig<T>| {
			config.tokens.iter().for_each(|currency_id| {
				config.endowed_accounts.iter().for_each(|account_id| {
					<Balance<T>>::insert(currency_id, account_id, &config.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 {
		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));
		}
Bryan Chen's avatar
Bryan Chen committed
	}
}

decl_error! {
	/// Error for token module.
	pub enum Error {
		BalanceTooLow,
		TotalIssuanceOverflow,
		AmountIntoBalanceFailed,
Bryan Chen's avatar
Bryan Chen committed
impl<T: Trait> Module<T> {}

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,
	) -> result::Result<(), Self::Error> {
		if Self::balance(currency_id, who).checked_sub(&amount).is_some() {
			Ok(())
		} else {
			Err(Error::BalanceTooLow)
		}
	}

Shaopeng Wang's avatar
Shaopeng Wang committed
	fn transfer(
		currency_id: Self::CurrencyId,
		from: &T::AccountId,
		to: &T::AccountId,
		amount: Self::Balance,
	) -> result::Result<(), Self::Error> {
		ensure!(Self::balance(currency_id, from) >= amount, Error::BalanceTooLow);
Shaopeng Wang's avatar
Shaopeng Wang committed

		if from != to {
			<Balance<T>>::mutate(currency_id, from, |balance| *balance -= amount);
			<Balance<T>>::mutate(currency_id, to, |balance| *balance += amount);
		}

		Ok(())
	}

	fn deposit(
Shaopeng Wang's avatar
Shaopeng Wang committed
		currency_id: Self::CurrencyId,
		who: &T::AccountId,
		amount: Self::Balance,
	) -> result::Result<(), Self::Error> {
Shaopeng Wang's avatar
Shaopeng Wang committed
		ensure!(
			Self::total_issuance(currency_id).checked_add(&amount).is_some(),
			Error::TotalIssuanceOverflow,
Shaopeng Wang's avatar
Shaopeng Wang committed
		);

		<TotalIssuance<T>>::mutate(currency_id, |v| *v += amount);
		<Balance<T>>::mutate(currency_id, who, |v| *v += amount);

		Ok(())
	}

	fn withdraw(
Shaopeng Wang's avatar
Shaopeng Wang committed
		currency_id: Self::CurrencyId,
		who: &T::AccountId,
		amount: Self::Balance,
	) -> result::Result<(), Self::Error> {
Shaopeng Wang's avatar
Shaopeng Wang committed
		ensure!(
			Self::balance(currency_id, who).checked_sub(&amount).is_some(),
Shaopeng Wang's avatar
Shaopeng Wang committed
		);

		<TotalIssuance<T>>::mutate(currency_id, |v| *v -= amount);
		<Balance<T>>::mutate(currency_id, who, |v| *v -= amount);

		Ok(())
	}

	fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance {
Shaopeng Wang's avatar
Shaopeng Wang committed
		let slashed_amount = Self::balance(currency_id, who).min(amount);
		<TotalIssuance<T>>::mutate(currency_id, |v| *v -= slashed_amount);
		<Balance<T>>::mutate(currency_id, who, |v| *v -= slashed_amount);
		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,
	) -> Result<(), Self::Error> {
		let by_balance =
			TryInto::<Self::Balance>::try_into(by_amount.abs()).map_err(|_| Error::AmountIntoBalanceFailed)?;
		if by_amount.is_positive() {
			Self::deposit(currency_id, who, by_balance)
		} else {
			Self::withdraw(currency_id, who, by_balance)
		}
	}
}