From cc1e7d12c4e2ab4f27f0596d20d67dec90533ab5 Mon Sep 17 00:00:00 2001 From: herryho Date: Tue, 28 Sep 2021 19:13:57 +0800 Subject: [PATCH 1/2] token-issuer --- Cargo.lock | 20 ++ Cargo.toml | 1 + pallets/token-issuer/Cargo.toml | 37 ++++ pallets/token-issuer/src/lib.rs | 274 ++++++++++++++++++++++++++++ pallets/token-issuer/src/mock.rs | 235 ++++++++++++++++++++++++ pallets/token-issuer/src/tests.rs | 159 ++++++++++++++++ pallets/token-issuer/src/weights.rs | 62 +++++++ runtime/asgard/Cargo.toml | 2 + runtime/asgard/src/lib.rs | 9 + runtime/bifrost/Cargo.toml | 2 + runtime/bifrost/src/lib.rs | 9 + 11 files changed, 810 insertions(+) create mode 100644 pallets/token-issuer/Cargo.toml create mode 100644 pallets/token-issuer/src/lib.rs create mode 100644 pallets/token-issuer/src/mock.rs create mode 100644 pallets/token-issuer/src/tests.rs create mode 100644 pallets/token-issuer/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 2cf89cd6c3..83354d6eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ "bifrost-runtime-common", "bifrost-salp", "bifrost-salp-rpc-runtime-api", + "bifrost-token-issuer", "bifrost-vesting", "bifrost-vsbond-auction", "bifrost-vtoken-mint", @@ -791,6 +792,7 @@ dependencies = [ "bifrost-runtime-common", "bifrost-salp", "bifrost-salp-rpc-runtime-api", + "bifrost-token-issuer", "bifrost-vesting", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -976,6 +978,24 @@ dependencies = [ "sp-api", ] +[[package]] +name = "bifrost-token-issuer" +version = "0.8.0" +dependencies = [ + "frame-support", + "frame-system", + "node-primitives", + "orml-currencies", + "orml-tokens 0.4.1-dev (git+https://github.com/open-web3-stack/open-runtime-module-library?rev=8d5432c3458702a7df14b374bddde43a2a20aa43)", + "orml-traits 0.4.1-dev (git+https://github.com/open-web3-stack/open-runtime-module-library?rev=8d5432c3458702a7df14b374bddde43a2a20aa43)", + "pallet-balances", + "pallet-collective", + "parity-scale-codec", + "sp-core 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.9)", + "sp-io", + "sp-runtime", +] + [[package]] name = "bifrost-vesting" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 30a9923bbd..cf3748931f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "pallets/vsbond-auction", "pallets/vtoken-mint", "pallets/vtoken-mint/rpc", + "pallets/token-issuer", "runtime/asgard", "runtime/bifrost", "utils/subkey", diff --git a/pallets/token-issuer/Cargo.toml b/pallets/token-issuer/Cargo.toml new file mode 100644 index 0000000000..34c23935ea --- /dev/null +++ b/pallets/token-issuer/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bifrost-token-issuer" +version = "0.8.0" +authors = ["Herry Ho "] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +node-primitives = { path = "../../node/primitives", default-features = false } +orml-traits = { version = "0.4.1-dev", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +orml-tokens = { version = "0.4.1-dev", default-features = false } +orml-currencies = { version = "0.4.1-dev", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +pallet-collective = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } + + +[features] +default = ["std"] +std = [ + "codec/std", + "node-primitives/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "sp-core/std", + "sp-runtime/std", + "sp-io/std", + "pallet-collective/std", + "orml-tokens/std", + "orml-currencies/std", + "pallet-balances/std" +] \ No newline at end of file diff --git a/pallets/token-issuer/src/lib.rs b/pallets/token-issuer/src/lib.rs new file mode 100644 index 0000000000..1a98e5a702 --- /dev/null +++ b/pallets/token-issuer/src/lib.rs @@ -0,0 +1,274 @@ +// This file is part of Bifrost. + +// Copyright (C) 2019-2021 Liebi Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +// pub use crate::imbalances::{NegativeImbalance, PositiveImbalance}; +extern crate alloc; + +use alloc::vec::Vec; + +use frame_support::{ensure, pallet_prelude::*, transactional}; +use frame_system::{pallet_prelude::*, WeightInfo}; +use orml_traits::{ + currency::TransferAll, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, + MultiReservableCurrency, +}; + +mod mock; +mod tests; + +pub use pallet::*; + +type BalanceOf = <::MultiCurrency as MultiCurrency< + ::AccountId, +>>::Balance; +type CurrencyIdOf = <::MultiCurrency as MultiCurrency< + ::AccountId, +>>::CurrencyId; +type AccountIdOf = ::AccountId; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + + type MultiCurrency: TransferAll + + MultiCurrencyExtended + + MultiLockableCurrency + + MultiReservableCurrency; + + /// The only origin that can edit token issuer list + type ControlOrigin: EnsureOrigin; + + /// Weight information for extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The balance is not enough + NotEnoughBalance, + /// The account doesn't exist in the whitelist. + NotExist, + /// The origin is not allowed to perform the operation. + NotAllowed, + } + + #[pallet::event] + #[pallet::metadata(BalanceOf = "Balance", CurrencyIdOf = "CurrencyId")] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Successful added a new account to the issue whitelist. \[account, currency_id]\ + AddedToIssueList(T::AccountId, CurrencyIdOf), + /// Successful remove an account from the issue whitelist. \[account, currency_id]\ + RemovedFromIssueList(T::AccountId, CurrencyIdOf), + /// Successful added a new account to the transfer whitelist. \[account, currency_id]\ + AddedToTransferList(T::AccountId, CurrencyIdOf), + /// Successful remove an account from the transfer whitelist. \[account, currency_id]\ + RemovedFromTransferList(T::AccountId, CurrencyIdOf), + /// Token issue success, \[currency_id, dest, amount\] + Issued(T::AccountId, CurrencyIdOf, BalanceOf), + /// Token transferred success, \[origin, dest, currency_id, amount\] + Transferred(T::AccountId, T::AccountId, CurrencyIdOf, BalanceOf), + } + + /// Accounts in the whitelist can issue the corresponding Currency. + #[pallet::storage] + #[pallet::getter(fn get_issue_whitelist)] + pub type IssueWhiteList = + StorageMap<_, Blake2_128Concat, CurrencyIdOf, Vec>>; + + /// Accounts in the whitelist can transfer the corresponding Currency. + #[pallet::storage] + #[pallet::getter(fn get_transfer_whitelist)] + pub type TransferWhiteList = + StorageMap<_, Blake2_128Concat, CurrencyIdOf, Vec>>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(1000)] + #[transactional] + pub fn add_to_issue_whitelist( + origin: OriginFor, + currency_id: CurrencyIdOf, + account: AccountIdOf, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + + let empty_vec: Vec> = Vec::new(); + if Self::get_issue_whitelist(currency_id) == None { + IssueWhiteList::::insert(currency_id, empty_vec); + } + + IssueWhiteList::::mutate(currency_id, |issue_whitelist| -> Result<(), Error> { + match issue_whitelist { + Some(issue_list) if !issue_list.contains(&account) => { + issue_list.push(account.clone()); + Self::deposit_event(Event::AddedToIssueList(account, currency_id)); + Ok(()) + }, + _ => Err(Error::::NotAllowed), + } + })?; + + Ok(()) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn remove_from_issue_whitelist( + origin: OriginFor, + currency_id: CurrencyIdOf, + account: AccountIdOf, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + + IssueWhiteList::::mutate(currency_id, |issue_whitelist| -> Result<(), Error> { + match issue_whitelist { + Some(issue_list) if issue_list.contains(&account) => { + issue_list.retain(|x| x.clone() != account); + Self::deposit_event(Event::RemovedFromIssueList(account, currency_id)); + Ok(()) + }, + _ => Err(Error::::NotExist), + } + })?; + + Ok(()) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn add_to_transfer_whitelist( + origin: OriginFor, + currency_id: CurrencyIdOf, + account: AccountIdOf, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + + let empty_vec: Vec> = Vec::new(); + if Self::get_transfer_whitelist(currency_id) == None { + TransferWhiteList::::insert(currency_id, empty_vec); + } + + TransferWhiteList::::mutate( + currency_id, + |transfer_whitelist| -> Result<(), Error> { + match transfer_whitelist { + Some(transfer_list) if !transfer_list.contains(&account) => { + transfer_list.push(account.clone()); + Self::deposit_event(Event::AddedToTransferList(account, currency_id)); + Ok(()) + }, + _ => Err(Error::::NotAllowed), + } + }, + )?; + + Ok(()) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn remove_from_transfer_whitelist( + origin: OriginFor, + currency_id: CurrencyIdOf, + account: AccountIdOf, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + + TransferWhiteList::::mutate( + currency_id, + |transfer_whitelist| -> Result<(), Error> { + match transfer_whitelist { + Some(transfer_list) if transfer_list.contains(&account) => { + transfer_list.retain(|x| x.clone() != account); + Self::deposit_event(Event::RemovedFromTransferList( + account, + currency_id, + )); + Ok(()) + }, + _ => Err(Error::::NotExist), + } + }, + )?; + + Ok(()) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn issue( + origin: OriginFor, + dest: AccountIdOf, + currency_id: CurrencyIdOf, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let issuer = ensure_signed(origin)?; + + let issue_whitelist = + Self::get_issue_whitelist(currency_id).ok_or(Error::::NotAllowed)?; + ensure!(issue_whitelist.contains(&issuer), Error::::NotAllowed); + + T::MultiCurrency::deposit(currency_id, &dest, amount)?; + + Self::deposit_event(Event::Issued(dest, currency_id, amount)); + Ok(()) + } + + /// Destroy some balance from an account. + /// + /// The dispatch origin for this call must be `Root` by the + /// transactor. + #[pallet::weight(1000)] + #[transactional] + pub fn transfer( + origin: OriginFor, + dest: AccountIdOf, + currency_id: CurrencyIdOf, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let issuer = ensure_signed(origin)?; + + let transfer_whitelist = + Self::get_transfer_whitelist(currency_id).ok_or(Error::::NotAllowed)?; + ensure!(transfer_whitelist.contains(&issuer), Error::::NotAllowed); + + let balance = T::MultiCurrency::free_balance(currency_id, &issuer); + ensure!(balance >= amount, Error::::NotEnoughBalance); + + T::MultiCurrency::transfer(currency_id, &issuer, &dest, amount)?; + + Self::deposit_event(Event::Transferred(issuer, dest, currency_id, amount)); + Ok(()) + } + } +} diff --git a/pallets/token-issuer/src/mock.rs b/pallets/token-issuer/src/mock.rs new file mode 100644 index 0000000000..a15201328e --- /dev/null +++ b/pallets/token-issuer/src/mock.rs @@ -0,0 +1,235 @@ +// This file is part of Bifrost. + +// Copyright (C) 2019-2021 Liebi Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![cfg(test)] +#![allow(non_upper_case_globals)] + +use frame_support::{parameter_types, traits::GenesisBuild}; +use node_primitives::{CurrencyId, TokenSymbol}; +use sp_core::{ + u32_trait::{_2, _3}, + H256, +}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, +}; + +use crate as bifrost_token_issuer; + +pub type BlockNumber = u64; +pub type Amount = i128; +pub type Balance = u64; + +pub type AccountId = AccountId32; +pub const BNC: CurrencyId = CurrencyId::Native(TokenSymbol::ASG); +pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); +pub const vDOT: CurrencyId = CurrencyId::VToken(TokenSymbol::DOT); +pub const KSM: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); +pub const ZLK: CurrencyId = CurrencyId::Token(TokenSymbol::ZLK); +pub const ALICE: AccountId = AccountId32::new([0u8; 32]); +pub const BOB: AccountId = AccountId32::new([1u8; 32]); +pub const CHARLIE: AccountId = AccountId32::new([3u8; 32]); + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Tokens: orml_tokens::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Currencies: orml_currencies::{Pallet, Call, Storage, Event}, + Council: pallet_collective::{Pallet, Call, Storage, Origin, Event, Config}, + TokenIssuer: bifrost_token_issuer::{Pallet, Call, Storage, Event} + } +); + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); +} +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountId; + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type Call = Call; + type DbWeight = (); + type Event = Event; + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type Origin = Origin; + type PalletInfo = PalletInfo; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Native(TokenSymbol::ASG); +} + +pub type AdaptedBasicCurrency = + orml_currencies::BasicCurrencyAdapter; + +impl orml_currencies::Config for Runtime { + type Event = Event; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Runtime { + type AccountStore = frame_system::Pallet; + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +orml_traits::parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + 0 + }; +} +impl orml_tokens::Config for Runtime { + type Amount = i128; + type Balance = Balance; + type CurrencyId = CurrencyId; + type DustRemovalWhitelist = (); + type Event = Event; + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type OnDust = orml_tokens::TransferDust; + type WeightInfo = (); +} + +parameter_types! { + pub const CouncilMotionDuration: BlockNumber = 2 * 7_200; + pub const CouncilMaxProposals: u32 = 100; + pub const CouncilMaxMembers: u32 = 100; +} + +impl pallet_collective::Config for Runtime { + type DefaultVote = pallet_collective::PrimeDefaultVote; + type Event = Event; + type MaxMembers = CouncilMaxMembers; + type MaxProposals = CouncilMaxProposals; + type MotionDuration = CouncilMotionDuration; + type Origin = Origin; + type Proposal = Call; + type WeightInfo = pallet_collective::weights::SubstrateWeight; +} + +impl bifrost_token_issuer::Config for Runtime { + type Event = Event; + type MultiCurrency = Currencies; + type ControlOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId>; + type WeightInfo = (); +} + +pub struct ExtBuilder { + endowed_accounts: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { endowed_accounts: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, endowed_accounts: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.endowed_accounts = endowed_accounts; + self + } + + pub fn one_hundred_for_alice_n_bob(self) -> Self { + self.balances(vec![ + (ALICE, BNC, 100), + (BOB, BNC, 100), + (CHARLIE, BNC, 100), + (ALICE, DOT, 100), + (ALICE, vDOT, 400), + (BOB, DOT, 100), + (BOB, KSM, 100), + ]) + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self + .endowed_accounts + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id == BNC) + .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .endowed_accounts + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id != BNC) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + // add ALICE, BOB, CHARLIE as the council member + pallet_collective::GenesisConfig:: { + members: vec![ALICE, BOB, CHARLIE], + phantom: Default::default(), + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/pallets/token-issuer/src/tests.rs b/pallets/token-issuer/src/tests.rs new file mode 100644 index 0000000000..14f7bf3204 --- /dev/null +++ b/pallets/token-issuer/src/tests.rs @@ -0,0 +1,159 @@ +// This file is part of Bifrost. + +// Copyright (C) 2019-2021 Liebi Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![cfg(test)] + +use frame_support::{assert_noop, assert_ok}; + +use crate::{mock::*, *}; + +fn initialize_charlie_as_issue_whitelist_member() { + // Add Charlie + assert_ok!(TokenIssuer::add_to_issue_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + // Issue some ZLK to Charlie's account + assert_ok!(TokenIssuer::issue(Origin::signed(CHARLIE), CHARLIE, ZLK, 1000)); +} + +#[test] +fn add_to_issue_whitelist_should_work() { + ExtBuilder::default().one_hundred_for_alice_n_bob().build().execute_with(|| { + // Charlie is not allowed to issue ZLK. + assert_noop!( + TokenIssuer::issue(Origin::signed(CHARLIE), ALICE, ZLK, 800), + Error::::NotAllowed + ); + // Chalie is added to the issue whitelist to have the ability of issuing ZLK. + assert_ok!(TokenIssuer::add_to_issue_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + assert_eq!(TokenIssuer::get_issue_whitelist(ZLK), Some(vec![CHARLIE])); + // Charlie succuessfully issue 800 unit of ZLK to Alice account + assert_ok!(TokenIssuer::issue(Origin::signed(CHARLIE), ALICE, ZLK, 800)); + assert_eq!(Tokens::free_balance(ZLK, &ALICE), 800); + }); +} + +#[test] +fn remove_from_issue_whitelist_should_work() { + ExtBuilder::default().one_hundred_for_alice_n_bob().build().execute_with(|| { + // Charlie is not in the issue whitelist + assert_noop!( + TokenIssuer::remove_from_issue_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + ), + Error::::NotExist + ); + // Add Charlie + assert_ok!(TokenIssuer::add_to_issue_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + + // Charlie succuessfully issue 800 unit of ZLK to Alice account + assert_ok!(TokenIssuer::issue(Origin::signed(CHARLIE), ALICE, ZLK, 800)); + assert_eq!(Tokens::free_balance(ZLK, &ALICE), 800); + + // Successfully remove Charlie + assert_ok!(TokenIssuer::remove_from_issue_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + // Charlie is no longer able to issue token to any account + assert_noop!( + TokenIssuer::issue(Origin::signed(CHARLIE), ALICE, ZLK, 800), + Error::::NotAllowed + ); + }); +} + +#[test] +fn add_to_transfer_whitelist_should_work() { + ExtBuilder::default().one_hundred_for_alice_n_bob().build().execute_with(|| { + initialize_charlie_as_issue_whitelist_member(); + + // Charlie is not allowed to transfer ZLK. + assert_noop!( + TokenIssuer::transfer(Origin::signed(CHARLIE), ALICE, ZLK, 800), + Error::::NotAllowed + ); + // Chalie is added to the transfer whitelist to have the ability of transferring ZLK. + assert_ok!(TokenIssuer::add_to_transfer_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + assert_eq!(TokenIssuer::get_transfer_whitelist(ZLK), Some(vec![CHARLIE])); + // Charlie succuessfully transfer 800 unit of ZLK to Alice account + assert_ok!(TokenIssuer::transfer(Origin::signed(CHARLIE), ALICE, ZLK, 800)); + assert_eq!(Tokens::free_balance(ZLK, &ALICE), 800); + // exceed balance + assert_noop!( + TokenIssuer::transfer(Origin::signed(CHARLIE), ALICE, ZLK, 300), + Error::::NotEnoughBalance + ); + }); +} + +#[test] +fn remove_from_transfer_whitelist_should_work() { + ExtBuilder::default().one_hundred_for_alice_n_bob().build().execute_with(|| { + initialize_charlie_as_issue_whitelist_member(); + + // Charlie is not in the transfer whitelist + assert_noop!( + TokenIssuer::remove_from_transfer_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + ), + Error::::NotExist + ); + // Add Charlie + assert_ok!(TokenIssuer::add_to_transfer_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + + // Charlie succuessfully transfer 800 unit of ZLK to Alice account + assert_ok!(TokenIssuer::transfer(Origin::signed(CHARLIE), ALICE, ZLK, 800)); + assert_eq!(Tokens::free_balance(ZLK, &ALICE), 800); + + // Successfully remove Charlie + assert_ok!(TokenIssuer::remove_from_transfer_whitelist( + pallet_collective::RawOrigin::Members(2, 3).into(), + ZLK, + CHARLIE + )); + // Charlie is no longer able to transfer token to any account + assert_noop!( + TokenIssuer::transfer(Origin::signed(CHARLIE), ALICE, ZLK, 800), + Error::::NotAllowed + ); + }); +} diff --git a/pallets/token-issuer/src/weights.rs b/pallets/token-issuer/src/weights.rs new file mode 100644 index 0000000000..b8fc6aa37d --- /dev/null +++ b/pallets/token-issuer/src/weights.rs @@ -0,0 +1,62 @@ +// This file is part of Bifrost. + +// Copyright (C) 2019-2021 Liebi Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + + +pub trait WeightInfo { + fn add_to_issue_whitelist() -> Weight; + fn remove_from_issue_whitelist() -> Weight; + fn add_to_transfer_whitelist() -> Weight; + fn remove_from_transfer_whitelist() -> Weight; + fn issue() -> Weight; + fn transfer() -> Weight; +} + +impl WeightInfo for () { + fn add_to_issue_whitelist() -> Weight { + (50_000_000 as Weight) + } + + fn remove_from_issue_whitelist() -> Weight { + (50_000_000 as Weight) + } + + fn add_to_transfer_whitelist() -> Weight { + (50_000_000 as Weight) + } + + fn remove_from_transfer_whitelist() -> Weight { + (50_000_000 as Weight) + } + + fn issue() -> Weight { + (50_000_000 as Weight) + } + + fn transfer() -> Weight { + (50_000_000 as Weight) + } +} \ No newline at end of file diff --git a/runtime/asgard/Cargo.toml b/runtime/asgard/Cargo.toml index c444c565ba..d6b6fbf216 100644 --- a/runtime/asgard/Cargo.toml +++ b/runtime/asgard/Cargo.toml @@ -92,6 +92,7 @@ bifrost-salp = { path = "../../pallets/salp", default-features = false } bifrost-salp-rpc-runtime-api = { path = "../../pallets/salp/rpc/runtime-api", default-features = false } bifrost-vsbond-auction = { path = "../../pallets/vsbond-auction", default-features = false } bifrost-vtoken-mint = { path = "../../pallets/vtoken-mint", default-features = false } +bifrost-token-issuer= { path = "../../pallets/token-issuer", default-features = false } pallet-vesting = { package = "bifrost-vesting", path = "../../pallets/vesting", default-features = false } xcm-support = { path = "../../xcm-support", default-features = false } @@ -180,6 +181,7 @@ std = [ "bifrost-vsbond-auction/std", "bifrost-vtoken-mint/std", "bifrost-liquidity-mining/std", + "bifrost-token-issuer/std", "xcm-support/std", "orml-currencies/std", "orml-traits/std", diff --git a/runtime/asgard/src/lib.rs b/runtime/asgard/src/lib.rs index 114eadd3cc..aac6a2848a 100644 --- a/runtime/asgard/src/lib.rs +++ b/runtime/asgard/src/lib.rs @@ -1165,6 +1165,14 @@ impl bifrost_liquidity_mining::Config for Runtime { type WeightInfo = (); } +impl bifrost_token_issuer::Config for Runtime { + type Event = Event; + type MultiCurrency = Currencies; + type ControlOrigin = + EnsureOneOf; + type WeightInfo = (); +} + // bifrost runtime end // zenlink runtime start @@ -1390,6 +1398,7 @@ construct_runtime! { Bancor: bifrost_bancor::{Pallet, Call, Storage, Event, Config} = 106, VSBondAuction: bifrost_vsbond_auction::{Pallet, Call, Storage, Event} = 107, LiquidityMining: bifrost_liquidity_mining::{Pallet, Call, Storage, Event} = 108, + TokenIssuer: bifrost_token_issuer::{Pallet, Call, Storage, Event} = 109, } } diff --git a/runtime/bifrost/Cargo.toml b/runtime/bifrost/Cargo.toml index 0cc55cbcc8..d8627c857d 100644 --- a/runtime/bifrost/Cargo.toml +++ b/runtime/bifrost/Cargo.toml @@ -86,6 +86,7 @@ bifrost-salp = { path = "../../pallets/salp", default-features = false } bifrost-salp-rpc-runtime-api = { path = "../../pallets/salp/rpc/runtime-api", default-features = false } pallet-vesting = { package = "bifrost-vesting", path = "../../pallets/vesting", default-features = false } bifrost-liquidity-mining = { path = "../../pallets/liquidity-mining", default-features = false } +bifrost-token-issuer = { path = "../../pallets/token-issuer", default-features = false } xcm-support = { path = "../../xcm-support", default-features = false } # orml @@ -163,6 +164,7 @@ std = [ "bifrost-salp/std", "bifrost-salp-rpc-runtime-api/std", "bifrost-liquidity-mining/std", + "bifrost-token-issuer/std", ] with-tracing = ["frame-executive/with-tracing"] diff --git a/runtime/bifrost/src/lib.rs b/runtime/bifrost/src/lib.rs index 88355547a0..e913ed3c6a 100644 --- a/runtime/bifrost/src/lib.rs +++ b/runtime/bifrost/src/lib.rs @@ -1100,6 +1100,14 @@ impl bifrost_liquidity_mining::Config for Runtime { type WeightInfo = weights::bifrost_liquidity_mining::WeightInfo; } +impl bifrost_token_issuer::Config for Runtime { + type Event = Event; + type MultiCurrency = Currencies; + type ControlOrigin = + EnsureOneOf; + type WeightInfo = (); +} + // Bifrost modules end construct_runtime! { @@ -1164,6 +1172,7 @@ construct_runtime! { FlexibleFee: bifrost_flexible_fee::{Pallet, Call, Storage, Event} = 100, Salp: bifrost_salp::{Pallet, Call, Storage, Event} = 105, LiquidityMining: bifrost_liquidity_mining::{Pallet, Call, Storage, Event} = 108, + TokenIssuer: bifrost_token_issuer::{Pallet, Call, Storage, Event} = 109, } } From a70e880b6b25c4b9a5313a7e65ad401cd10042d1 Mon Sep 17 00:00:00 2001 From: herryho Date: Wed, 29 Sep 2021 10:51:47 +0800 Subject: [PATCH 2/2] fixes --- pallets/token-issuer/src/lib.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pallets/token-issuer/src/lib.rs b/pallets/token-issuer/src/lib.rs index 1a98e5a702..d15bfa287c 100644 --- a/pallets/token-issuer/src/lib.rs +++ b/pallets/token-issuer/src/lib.rs @@ -27,10 +27,7 @@ use alloc::vec::Vec; use frame_support::{ensure, pallet_prelude::*, transactional}; use frame_system::{pallet_prelude::*, WeightInfo}; -use orml_traits::{ - currency::TransferAll, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, - MultiReservableCurrency, -}; +use orml_traits::MultiCurrency; mod mock; mod tests; @@ -52,11 +49,8 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type Event: From> + IsType<::Event>; - - type MultiCurrency: TransferAll - + MultiCurrencyExtended - + MultiLockableCurrency - + MultiReservableCurrency; + /// Currecny operation handler + type MultiCurrency: MultiCurrency>; /// The only origin that can edit token issuer list type ControlOrigin: EnsureOrigin; @@ -256,18 +250,18 @@ pub mod pallet { currency_id: CurrencyIdOf, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { - let issuer = ensure_signed(origin)?; + let transferrer = ensure_signed(origin)?; let transfer_whitelist = Self::get_transfer_whitelist(currency_id).ok_or(Error::::NotAllowed)?; - ensure!(transfer_whitelist.contains(&issuer), Error::::NotAllowed); + ensure!(transfer_whitelist.contains(&transferrer), Error::::NotAllowed); - let balance = T::MultiCurrency::free_balance(currency_id, &issuer); + let balance = T::MultiCurrency::free_balance(currency_id, &transferrer); ensure!(balance >= amount, Error::::NotEnoughBalance); - T::MultiCurrency::transfer(currency_id, &issuer, &dest, amount)?; + T::MultiCurrency::transfer(currency_id, &transferrer, &dest, amount)?; - Self::deposit_event(Event::Transferred(issuer, dest, currency_id, amount)); + Self::deposit_event(Event::Transferred(transferrer, dest, currency_id, amount)); Ok(()) } }