From 7d2756bf0b35fcdf68bbb665dbee5d80905b93ed Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Thu, 5 Sep 2024 17:22:52 +0000 Subject: [PATCH 1/5] init halving mint --- Cargo.lock | 17 ++ Cargo.toml | 2 + pallets/halving-mint/Cargo.toml | 55 ++++++ pallets/halving-mint/src/lib.rs | 305 +++++++++++++++++++++++++++++ pallets/halving-mint/src/mock.rs | 95 +++++++++ pallets/halving-mint/src/tests.rs | 169 ++++++++++++++++ pallets/halving-mint/src/traits.rs | 12 ++ 7 files changed, 655 insertions(+) create mode 100644 pallets/halving-mint/Cargo.toml create mode 100644 pallets/halving-mint/src/lib.rs create mode 100644 pallets/halving-mint/src/mock.rs create mode 100644 pallets/halving-mint/src/tests.rs create mode 100644 pallets/halving-mint/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 72cdab3888..761c5fefb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7554,6 +7554,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-halving-mint" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-identity" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index be1d357ec4..3fc351a397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ 'pallets/teebag', 'pallets/vc-management', 'pallets/xcm-asset-manager', + 'pallets/halving-mint', 'precompiles/*', 'primitives/core', 'primitives/core/proc-macros', @@ -270,6 +271,7 @@ pallet-evm-precompile-bridge-transfer = { path = "precompiles/bridge-transfer", pallet-evm-precompile-parachain-staking = { path = "precompiles/parachain-staking", default-features = false } pallet-evm-precompile-score-staking = { path = "precompiles/score-staking", default-features = false } pallet-evm-assertions = { path = "pallets/evm-assertions", default-features = false } +pallet-halving-mint = { path = "pallets/halving-mint", default-features = false } [patch.crates-io] ring = { git = "https://github.com/betrusted-io/ring-xous", branch = "0.16.20-cleanup" } diff --git a/pallets/halving-mint/Cargo.toml b/pallets/halving-mint/Cargo.toml new file mode 100644 index 0000000000..4b3a95ca01 --- /dev/null +++ b/pallets/halving-mint/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = 'pallet-halving-mint' +description = 'pallet to mint tokens in a halving way similar to BTC' +authors = ['Trust Computing GmbH '] +edition = '2021' +homepage = 'https://litentry.com/' +license = 'GPL-3.0' +repository = 'https://github.com/litentry/litentry-parachain' +version = '0.1.0' + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +serde = { workspace = true } + +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +sp-core = { workspace = true } +sp-io = { workspace = true } +pallet-balances = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "serde/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "pallet-balances/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/halving-mint/src/lib.rs b/pallets/halving-mint/src/lib.rs new file mode 100644 index 0000000000..ae62ba3e6c --- /dev/null +++ b/pallets/halving-mint/src/lib.rs @@ -0,0 +1,305 @@ +//! # Pallet halving-mint +//! +//! This pallet mints the (native) token in a halving way. +//! +//! It will be parameterized with the total issuance count and halving interval (in blocks), +//! The minted token is deposited to the `beneficiary` account, which should be a privated +//! account derived from the PalletId(similar to treasury). There's a trait `OnTokenMinted` +//! to hook the callback into other pallet. +//! +//! The main parameters: +//! - total issuance +//! - halving interval +//! - beneficiary account +//! are defined as runtime constants. It implies that once onboarded, they can be changed +//! only by runtime upgrade. Thus it has a stronger guarantee in comparison to extrinsics. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] + +use frame_support::traits::Currency; +pub use pallet::*; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +mod traits; +pub use traits::OnTokenMinted; + +pub type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; + +/// an on/off flag, used in both `MintState` and `OnTokenMintedState` +#[derive( + PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, Encode, Decode, Debug, TypeInfo, +)] +pub enum State { + #[default] + #[codec(index = 0)] + Stopped, + #[codec(index = 1)] + Running, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ReservableCurrency, StorageVersion}, + PalletId, + }; + use frame_system::pallet_prelude::{BlockNumberFor, *}; + use sp_runtime::{ + traits::{AccountIdConversion, One, Zero}, + Saturating, + }; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + type Currency: Currency + ReservableCurrency; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + /// The origin to control the minting configuration + type ManagerOrigin: EnsureOrigin; + /// The total issuance of the (native) token + #[pallet::constant] + type TotalIssuance: Get>; + /// Halving internal in blocks, we force u32 type, BlockNumberFor implements + /// AtLeast32BitUnsigned so it's safe + #[pallet::constant] + type HalvingInterval: Get; + /// The beneficiary PalletId, used for deriving its sovereign AccountId + #[pallet::constant] + type BeneficiaryId: Get; + /// Hook for other pallets to deal with OnTokenMinted event + type OnTokenMinted: OnTokenMinted>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + MintStateChanged { new_state: State }, + OnTokenMintedStateChanged { new_state: State }, + MintStarted { start_block: BlockNumberFor }, + Minted { to: T::AccountId, amount: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + MintStateUnchanged, + OnTokenMintedStateUnchanged, + MintAlreadyStarted, + MintNotStarted, + StartBlockTooEarly, + SkippedBlocksOverflow, + } + + #[pallet::storage] + #[pallet::getter(fn mint_state)] + pub type MintState, I: 'static = ()> = StorageValue<_, State, ValueQuery>; + + /// If the `OnTokenMinted` callback is stopped or not + #[pallet::storage] + #[pallet::getter(fn on_token_minted_state)] + pub type OnTokenMintedState, I: 'static = ()> = StorageValue<_, State, ValueQuery>; + + /// the block number from which the minting is started, `None` means minting + /// is not started yet + #[pallet::storage] + #[pallet::getter(fn start_block)] + pub type StartBlock, I: 'static = ()> = + StorageValue<_, BlockNumberFor, OptionQuery>; + + /// Number of skipped blocks being counted when `MintState` is `Stopped` + #[pallet::storage] + #[pallet::getter(fn skipped_blocks)] + pub type SkippedBlocks, I: 'static = ()> = + StorageValue<_, BlockNumberFor, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub mint_state: State, + pub on_token_minted_state: State, + pub start_block: Option>, + #[serde(skip)] + pub phantom: PhantomData, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + mint_state: State::Stopped, + on_token_minted_state: State::Stopped, + start_block: None, + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) { + MintState::::put(self.mint_state); + OnTokenMintedState::::put(self.on_token_minted_state); + if let Some(n) = self.start_block { + StartBlock::::put(n); + } + } + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + let mut weight = Weight::zero(); + if let Some(start_block) = Self::start_block() { + if Self::mint_state() == State::Running { + let skipped_blocks = Self::skipped_blocks(); + // 3 reads: `mint_state`, `start_block`, `skipped_blocks` + weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 0)); + + if now < start_block.saturating_add(skipped_blocks) { + return weight; + } + + let halving_interval = T::HalvingInterval::get(); + + // calculate the amount of initially minted tokens before first halving + let mut minted = T::TotalIssuance::get() / (halving_interval * 2).into(); + // halving round index + let halving_round = (now - start_block.saturating_add(skipped_blocks)) + / halving_interval.into(); + // beneficiary account + let to = Self::beneficiary_account(); + + // 2 reads: `total_issuance`, `halving_interval` + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 0)); + + // if we want to use bit shift, we need to: + // 1. know the overlfow limit similar to what bitcoin has: `if (halvings >= + // 64) return 0;` so 127 for u128 + // 2. coerce the `halving_round` to u32 + // but both `halving_round` and `minted` are associated types that need to be + // defined during runtime binding thus plain division is used + let mut i = BlockNumberFor::::zero(); + while i < halving_round { + minted = minted / 2u32.into(); + i += BlockNumberFor::::one(); + } + + // theoreticlaly we can deal with the minted tokens directly in the trait impl + // pallet, without depositing to an account first. + // but the purpose of having the extra logic is to make sure the tokens are + // minted to the beneficiary account, regardless of what happens callback. Even + // if the callback errors out, it's guaranteed that the tokens are + // already minted (and stored on an account), which resonates with the "fair + // launch" concept. + // + // Also imagine there's no callback impl, in this case the tokens will still be + // minted and accumulated. + let _ = T::Currency::deposit_creating(&to, minted); + Self::deposit_event(Event::Minted { to: to.clone(), amount: minted }); + if Self::on_token_minted_state() == State::Running { + weight = weight.saturating_add(T::OnTokenMinted::token_minted(to, minted)); + } + // 1 read: `on_token_minted_state` + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + } else { + // we should have minted tokens but it's forcibly stopped + let skipped_blocks = + Self::skipped_blocks().saturating_add(BlockNumberFor::::one()); + SkippedBlocks::::put(skipped_blocks); + // 1 read, 1 write: `SkippedBlocks` + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + } + } + weight + } + } + + // TODO: benchmarking and WeightInfo + // IMO it's not **that** bad to use constant weight for extrinsics now as they are simple calls + // and should only be called once or very few times. + #[pallet::call] + impl, I: 'static> Pallet { + /// Set the state of the minting, it essentially "pause" and "resume" the minting process. + #[pallet::call_index(0)] + #[pallet::weight((195_000_000, DispatchClass::Normal))] + pub fn set_mint_state(origin: OriginFor, state: State) -> DispatchResultWithPostInfo { + T::ManagerOrigin::ensure_origin(origin)?; + ensure!(StartBlock::::get().is_some(), Error::::MintNotStarted); + ensure!(state != Self::mint_state(), Error::::MintStateUnchanged); + MintState::::put(state); + Self::deposit_event(Event::MintStateChanged { new_state: state }); + Ok(Pays::No.into()) + } + + #[pallet::call_index(1)] + #[pallet::weight((195_000_000, DispatchClass::Normal))] + pub fn set_on_token_minted_state( + origin: OriginFor, + state: State, + ) -> DispatchResultWithPostInfo { + T::ManagerOrigin::ensure_origin(origin)?; + ensure!(StartBlock::::get().is_some(), Error::::MintNotStarted); + ensure!( + state != Self::on_token_minted_state(), + Error::::OnTokenMintedStateUnchanged + ); + OnTokenMintedState::::put(state); + Self::deposit_event(Event::OnTokenMintedStateChanged { new_state: state }); + Ok(Pays::No.into()) + } + + /// Start mint from next block, this is the earliest block the next minting can happen, + /// as we already missed the intialization of current block and we don't do retroactive + /// minting + #[pallet::call_index(2)] + #[pallet::weight((195_000_000, DispatchClass::Normal))] + pub fn start_mint_from_next_block(origin: OriginFor) -> DispatchResultWithPostInfo { + Self::start_mint_from_block( + origin, + frame_system::Pallet::::block_number() + BlockNumberFor::::one(), + ) + } + + /// Start mint from a given block that is larger than the current block number + #[pallet::call_index(3)] + #[pallet::weight((195_000_000, DispatchClass::Normal))] + pub fn start_mint_from_block( + origin: OriginFor, + start_block: BlockNumberFor, + ) -> DispatchResultWithPostInfo { + T::ManagerOrigin::ensure_origin(origin)?; + ensure!(StartBlock::::get().is_none(), Error::::MintAlreadyStarted); + ensure!( + start_block > frame_system::Pallet::::block_number(), + Error::::StartBlockTooEarly + ); + MintState::::put(State::Running); + OnTokenMintedState::::put(State::Running); + StartBlock::::put(start_block); + Self::deposit_event(Event::MintStarted { start_block }); + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet { + pub fn beneficiary_account() -> T::AccountId { + T::BeneficiaryId::get().into_account_truncating() + } + } +} diff --git a/pallets/halving-mint/src/mock.rs b/pallets/halving-mint/src/mock.rs new file mode 100644 index 0000000000..9071c6ebd3 --- /dev/null +++ b/pallets/halving-mint/src/mock.rs @@ -0,0 +1,95 @@ +use crate::{self as pallet_halving_mint, OnTokenMinted}; +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU64, Currency, ExistenceRequirement, Hooks}, + weights::Weight, + PalletId, +}; +use sp_core::ConstU32; +use sp_runtime::BuildStorage; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + HalvingMint: pallet_halving_mint, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 2; + pub const MaxLocks: u32 = 10; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type MaxHolds = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +parameter_types! { + pub const BeneficiaryId: PalletId = PalletId(*b"can/hlvm"); +} + +impl pallet_halving_mint::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = frame_system::EnsureRoot; + type TotalIssuance = ConstU64<1000>; + type HalvingInterval = ConstU32<10>; + type BeneficiaryId = BeneficiaryId; + type OnTokenMinted = TransferOnTokenMinted; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = + frame_system::GenesisConfig::::default().build_storage().unwrap().into(); + ext.execute_with(|| { + System::set_block_number(1); + let _ = Balances::deposit_creating(&HalvingMint::beneficiary_account(), 10); + }); + ext +} + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + if System::block_number() > 1 { + System::on_finalize(System::block_number()); + } + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + HalvingMint::on_initialize(System::block_number()); + } +} + +pub struct TransferOnTokenMinted(sp_std::marker::PhantomData); + +impl OnTokenMinted for TransferOnTokenMinted +where + T: frame_system::Config + pallet_balances::Config, +{ + fn token_minted(beneficiary: T::AccountId, amount: T::Balance) -> Weight { + let _ = Balances::transfer(&beneficiary, &1, amount, ExistenceRequirement::AllowDeath); + Weight::zero() + } +} diff --git a/pallets/halving-mint/src/tests.rs b/pallets/halving-mint/src/tests.rs new file mode 100644 index 0000000000..a552f51211 --- /dev/null +++ b/pallets/halving-mint/src/tests.rs @@ -0,0 +1,169 @@ +use crate::{mock::*, Error, Event, State}; +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn set_mint_state_check_works() { + new_test_ext().execute_with(|| { + assert_eq!(HalvingMint::mint_state(), State::Stopped); + assert_noop!( + HalvingMint::set_mint_state(RuntimeOrigin::signed(1), State::Running), + sp_runtime::DispatchError::BadOrigin, + ); + assert_noop!( + HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), + Error::::MintNotStarted, + ); + assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + assert_eq!(HalvingMint::mint_state(), State::Running); + assert_noop!( + HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), + Error::::MintStateUnchanged, + ); + assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Stopped)); + assert_eq!(HalvingMint::mint_state(), State::Stopped); + System::assert_last_event(Event::MintStateChanged { new_state: State::Stopped }.into()); + }); +} + +#[test] +fn start_mint_too_early_fails() { + new_test_ext().execute_with(|| { + assert_eq!(System::block_number(), 1); + assert_noop!( + HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 0), + Error::::StartBlockTooEarly, + ); + assert_noop!( + HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 1), + Error::::StartBlockTooEarly, + ); + assert_ok!(HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 2)); + System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); + }); +} + +#[test] +fn halving_mint_works() { + new_test_ext().execute_with(|| { + let beneficiary = HalvingMint::beneficiary_account(); + + assert_eq!(System::block_number(), 1); + assert_eq!(Balances::total_issuance(), 10); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); + + run_to_block(2); + // 50 tokens are minted + assert_eq!(Balances::total_issuance(), 60); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 50); + + run_to_block(11); + assert_eq!(Balances::total_issuance(), 510); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 500); + + run_to_block(12); + // the first halving + assert_eq!(Balances::total_issuance(), 535); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 525); + + run_to_block(22); + // the second halving + assert_eq!(Balances::total_issuance(), 772); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 762); + + run_to_block(52); + // the fifth halving - only 1 token is minted + assert_eq!(Balances::total_issuance(), 971); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 961); + + run_to_block(62); + // the sixth halving - but 0 tokens will be minted + assert_eq!(Balances::total_issuance(), 980); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 970); + + run_to_block(1_000); + // no changes since the sixth halving, the total minted token will be fixated on 980, + // the "missing" 20 comes from the integer division and the total_issuance is too small. + // + // we'll have much accurate result in reality where token unit is 18 decimal + assert_eq!(Balances::total_issuance(), 980); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 970); + }); +} + +#[test] +fn set_on_token_minted_state_works() { + new_test_ext().execute_with(|| { + let beneficiary = HalvingMint::beneficiary_account(); + + assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + assert_ok!(HalvingMint::set_on_token_minted_state(RuntimeOrigin::root(), State::Stopped)); + System::assert_last_event( + Event::OnTokenMintedStateChanged { new_state: State::Stopped }.into(), + ); + + run_to_block(2); + // 50 tokens are minted, but none is transferred away + assert_eq!(Balances::total_issuance(), 60); + assert_eq!(Balances::free_balance(&beneficiary), 60); + assert_eq!(Balances::free_balance(&1), 0); + + run_to_block(10); + assert_ok!(HalvingMint::set_on_token_minted_state(RuntimeOrigin::root(), State::Running)); + System::assert_last_event( + Event::OnTokenMintedStateChanged { new_state: State::Running }.into(), + ); + + run_to_block(11); + // start to transfer token + assert_eq!(Balances::total_issuance(), 510); + assert_eq!(Balances::free_balance(&beneficiary), 460); + assert_eq!(Balances::free_balance(&1), 50); + }); +} + +#[test] +fn set_mint_state_works() { + new_test_ext().execute_with(|| { + let beneficiary = HalvingMint::beneficiary_account(); + + assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + + run_to_block(2); + assert_eq!(Balances::total_issuance(), 60); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 50); + // stop the minting + assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Stopped)); + + run_to_block(3); + // no new tokens should be minted + assert_eq!(Balances::total_issuance(), 60); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 50); + + run_to_block(4); + // resume the minting + assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running)); + + run_to_block(5); + assert_eq!(Balances::total_issuance(), 110); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 100); + assert_eq!(HalvingMint::skipped_blocks(), 2); + + // the first halving should be delayed to block 14 + run_to_block(14); + assert_eq!(Balances::total_issuance(), 535); + assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(&1), 525); + }); +} diff --git a/pallets/halving-mint/src/traits.rs b/pallets/halving-mint/src/traits.rs new file mode 100644 index 0000000000..691b363a12 --- /dev/null +++ b/pallets/halving-mint/src/traits.rs @@ -0,0 +1,12 @@ +/// Traits for pallet-halving-mint +use frame_support::pallet_prelude::Weight; + +pub trait OnTokenMinted { + fn token_minted(beneficiary: AccountId, amount: Balance) -> Weight; +} + +impl OnTokenMinted for () { + fn token_minted(_beneficiary: AccountId, _amount: Balance) -> Weight { + Weight::zero() + } +} From 92a7a52b92c43fd74f530cd0378609b77c709e9a Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Fri, 6 Sep 2024 06:38:44 +0000 Subject: [PATCH 2/5] fix test --- pallets/halving-mint/src/mock.rs | 71 ++++++++++++++++++++----------- pallets/halving-mint/src/tests.rs | 10 ++--- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/pallets/halving-mint/src/mock.rs b/pallets/halving-mint/src/mock.rs index 9071c6ebd3..33b6ad6d29 100644 --- a/pallets/halving-mint/src/mock.rs +++ b/pallets/halving-mint/src/mock.rs @@ -1,28 +1,48 @@ -use crate::{self as pallet_halving_mint, OnTokenMinted}; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU64, Currency, ExistenceRequirement, Hooks}, - weights::Weight, - PalletId, +use crate::{self as pallet_halving_mint, Instance1, OnTokenMinted}; +use frame_support::pallet_prelude::*; +use frame_support::{construct_runtime, parameter_types, PalletId}; +use sp_core::{ConstU32, ConstU64, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -use sp_core::ConstU32; -use sp_runtime::BuildStorage; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, Balances: pallet_balances, - HalvingMint: pallet_halving_mint, + HalvingMint: pallet_halving_mint::, } ); -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +parameter_types! { + pub const BlockHashCount: u32 = 250; +} impl frame_system::Config for Test { - type Block = Block; + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Block = frame_system::mocking::MockBlock; + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { @@ -31,27 +51,26 @@ parameter_types! { } impl pallet_balances::Config for Test { + type MaxLocks = (); type Balance = u64; type DustRemoval = (); type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); - type MaxLocks = MaxLocks; type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; + type ReserveIdentifier = (); type FreezeIdentifier = (); - type MaxFreezes = (); type MaxHolds = (); + type MaxFreezes = (); type RuntimeHoldReason = (); - type RuntimeFreezeReason = (); } parameter_types! { - pub const BeneficiaryId: PalletId = PalletId(*b"can/hlvm"); + pub const BeneficiaryId: PalletId = PalletId(*b"lty/hlvm"); } -impl pallet_halving_mint::Config for Test { +impl pallet_halving_mint::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type ManagerOrigin = frame_system::EnsureRoot; @@ -62,11 +81,15 @@ impl pallet_halving_mint::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = - frame_system::GenesisConfig::::default().build_storage().unwrap().into(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(HalvingMint::beneficiary_account(), 10)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { System::set_block_number(1); - let _ = Balances::deposit_creating(&HalvingMint::beneficiary_account(), 10); }); ext } @@ -89,7 +112,7 @@ where T: frame_system::Config + pallet_balances::Config, { fn token_minted(beneficiary: T::AccountId, amount: T::Balance) -> Weight { - let _ = Balances::transfer(&beneficiary, &1, amount, ExistenceRequirement::AllowDeath); + let _ = Balances::transfer(RuntimeOrigin::signed(beneficiary), 1, amount); Weight::zero() } } diff --git a/pallets/halving-mint/src/tests.rs b/pallets/halving-mint/src/tests.rs index a552f51211..673183fa74 100644 --- a/pallets/halving-mint/src/tests.rs +++ b/pallets/halving-mint/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{mock::*, Error, Event, State}; +use crate::{mock::*, Error, Event, Instance1, State}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -11,13 +11,13 @@ fn set_mint_state_check_works() { ); assert_noop!( HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), - Error::::MintNotStarted, + Error::::MintNotStarted, ); assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); assert_eq!(HalvingMint::mint_state(), State::Running); assert_noop!( HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), - Error::::MintStateUnchanged, + Error::::MintStateUnchanged, ); assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Stopped)); assert_eq!(HalvingMint::mint_state(), State::Stopped); @@ -31,11 +31,11 @@ fn start_mint_too_early_fails() { assert_eq!(System::block_number(), 1); assert_noop!( HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 0), - Error::::StartBlockTooEarly, + Error::::StartBlockTooEarly, ); assert_noop!( HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 1), - Error::::StartBlockTooEarly, + Error::::StartBlockTooEarly, ); assert_ok!(HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 2)); System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); From 836bf49b5f54e90b8e7f89733909b51d1486f21a Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Fri, 6 Sep 2024 06:40:57 +0000 Subject: [PATCH 3/5] fix test --- pallets/halving-mint/src/lib.rs | 2 +- pallets/halving-mint/src/tests.rs | 54 +++++++++++++++---------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pallets/halving-mint/src/lib.rs b/pallets/halving-mint/src/lib.rs index ae62ba3e6c..a090db9a0a 100644 --- a/pallets/halving-mint/src/lib.rs +++ b/pallets/halving-mint/src/lib.rs @@ -196,7 +196,7 @@ pub mod pallet { // defined during runtime binding thus plain division is used let mut i = BlockNumberFor::::zero(); while i < halving_round { - minted = minted / 2u32.into(); + minted /= 2u32.into(); i += BlockNumberFor::::one(); } diff --git a/pallets/halving-mint/src/tests.rs b/pallets/halving-mint/src/tests.rs index 673183fa74..b6d049cdb1 100644 --- a/pallets/halving-mint/src/tests.rs +++ b/pallets/halving-mint/src/tests.rs @@ -49,44 +49,44 @@ fn halving_mint_works() { assert_eq!(System::block_number(), 1); assert_eq!(Balances::total_issuance(), 10); - assert_eq!(Balances::free_balance(&beneficiary), 10); + assert_eq!(Balances::free_balance(beneficiary), 10); assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); run_to_block(2); // 50 tokens are minted assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 50); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 50); run_to_block(11); assert_eq!(Balances::total_issuance(), 510); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 500); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 500); run_to_block(12); // the first halving assert_eq!(Balances::total_issuance(), 535); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 525); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 525); run_to_block(22); // the second halving assert_eq!(Balances::total_issuance(), 772); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 762); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 762); run_to_block(52); // the fifth halving - only 1 token is minted assert_eq!(Balances::total_issuance(), 971); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 961); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 961); run_to_block(62); // the sixth halving - but 0 tokens will be minted assert_eq!(Balances::total_issuance(), 980); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 970); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 970); run_to_block(1_000); // no changes since the sixth halving, the total minted token will be fixated on 980, @@ -94,8 +94,8 @@ fn halving_mint_works() { // // we'll have much accurate result in reality where token unit is 18 decimal assert_eq!(Balances::total_issuance(), 980); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 970); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 970); }); } @@ -113,8 +113,8 @@ fn set_on_token_minted_state_works() { run_to_block(2); // 50 tokens are minted, but none is transferred away assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(&beneficiary), 60); - assert_eq!(Balances::free_balance(&1), 0); + assert_eq!(Balances::free_balance(beneficiary), 60); + assert_eq!(Balances::free_balance(1), 0); run_to_block(10); assert_ok!(HalvingMint::set_on_token_minted_state(RuntimeOrigin::root(), State::Running)); @@ -125,8 +125,8 @@ fn set_on_token_minted_state_works() { run_to_block(11); // start to transfer token assert_eq!(Balances::total_issuance(), 510); - assert_eq!(Balances::free_balance(&beneficiary), 460); - assert_eq!(Balances::free_balance(&1), 50); + assert_eq!(Balances::free_balance(beneficiary), 460); + assert_eq!(Balances::free_balance(1), 50); }); } @@ -139,16 +139,16 @@ fn set_mint_state_works() { run_to_block(2); assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 50); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 50); // stop the minting assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Stopped)); run_to_block(3); // no new tokens should be minted assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 50); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 50); run_to_block(4); // resume the minting @@ -156,14 +156,14 @@ fn set_mint_state_works() { run_to_block(5); assert_eq!(Balances::total_issuance(), 110); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 100); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 100); assert_eq!(HalvingMint::skipped_blocks(), 2); // the first halving should be delayed to block 14 run_to_block(14); assert_eq!(Balances::total_issuance(), 535); - assert_eq!(Balances::free_balance(&beneficiary), 10); - assert_eq!(Balances::free_balance(&1), 525); + assert_eq!(Balances::free_balance(beneficiary), 10); + assert_eq!(Balances::free_balance(1), 525); }); } From 0b5e28b1c252a98379695850bc3601cbb5e6eba2 Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Tue, 10 Sep 2024 08:02:19 +0000 Subject: [PATCH 4/5] fix tests --- Cargo.lock | 1 + pallets/halving-mint/Cargo.toml | 2 + pallets/halving-mint/src/lib.rs | 92 +++++++++++++---- pallets/halving-mint/src/mock.rs | 83 +++++++++++++-- pallets/halving-mint/src/tests.rs | 161 +++++++++++++++++++---------- pallets/halving-mint/src/traits.rs | 24 ++++- 6 files changed, 275 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 761c5fefb2..801db37e60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7561,6 +7561,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-assets", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/pallets/halving-mint/Cargo.toml b/pallets/halving-mint/Cargo.toml index 4b3a95ca01..cc2dfeb55f 100644 --- a/pallets/halving-mint/Cargo.toml +++ b/pallets/halving-mint/Cargo.toml @@ -19,6 +19,7 @@ serde = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-assets = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -40,6 +41,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "pallet-assets/std", "pallet-balances/std", ] runtime-benchmarks = [ diff --git a/pallets/halving-mint/src/lib.rs b/pallets/halving-mint/src/lib.rs index a090db9a0a..e3d82912f9 100644 --- a/pallets/halving-mint/src/lib.rs +++ b/pallets/halving-mint/src/lib.rs @@ -1,3 +1,19 @@ +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry 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. +// +// Litentry 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 Litentry. If not, see . + //! # Pallet halving-mint //! //! This pallet mints the (native) token in a halving way. @@ -17,7 +33,10 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::too_many_arguments)] -use frame_support::traits::Currency; +use frame_support::traits::tokens::{ + fungibles::{metadata::Mutate as MMutate, Create, Inspect, Mutate}, + AssetId, Balance, +}; pub use pallet::*; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -32,9 +51,6 @@ mod tests; mod traits; pub use traits::OnTokenMinted; -pub type BalanceOf = - <>::Currency as Currency<::AccountId>>::Balance; - /// an on/off flag, used in both `MintState` and `OnTokenMintedState` #[derive( PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, Encode, Decode, Debug, TypeInfo, @@ -50,11 +66,7 @@ pub enum State { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{ReservableCurrency, StorageVersion}, - PalletId, - }; + use frame_support::{pallet_prelude::*, traits::StorageVersion, PalletId}; use frame_system::pallet_prelude::{BlockNumberFor, *}; use sp_runtime::{ traits::{AccountIdConversion, One, Zero}, @@ -70,14 +82,19 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type Currency: Currency + ReservableCurrency; + type Assets: Inspect + + Mutate + + Create + + MMutate; + type AssetId: AssetId + Copy; + type AssetBalance: Balance; type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The origin to control the minting configuration type ManagerOrigin: EnsureOrigin; /// The total issuance of the (native) token #[pallet::constant] - type TotalIssuance: Get>; + type TotalIssuance: Get; /// Halving internal in blocks, we force u32 type, BlockNumberFor implements /// AtLeast32BitUnsigned so it's safe #[pallet::constant] @@ -86,7 +103,7 @@ pub mod pallet { #[pallet::constant] type BeneficiaryId: Get; /// Hook for other pallets to deal with OnTokenMinted event - type OnTokenMinted: OnTokenMinted>; + type OnTokenMinted: OnTokenMinted; } #[pallet::event] @@ -94,8 +111,8 @@ pub mod pallet { pub enum Event, I: 'static = ()> { MintStateChanged { new_state: State }, OnTokenMintedStateChanged { new_state: State }, - MintStarted { start_block: BlockNumberFor }, - Minted { to: T::AccountId, amount: BalanceOf }, + MintStarted { asset_id: T::AssetId, start_block: BlockNumberFor }, + Minted { asset_id: T::AssetId, to: T::AccountId, amount: T::AssetBalance }, } #[pallet::error] @@ -108,6 +125,10 @@ pub mod pallet { SkippedBlocksOverflow, } + #[pallet::storage] + #[pallet::getter(fn mint_asset_id)] + pub type MintAssetId, I: 'static = ()> = StorageValue<_, T::AssetId, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn mint_state)] pub type MintState, I: 'static = ()> = StorageValue<_, State, ValueQuery>; @@ -210,13 +231,23 @@ pub mod pallet { // // Also imagine there's no callback impl, in this case the tokens will still be // minted and accumulated. - let _ = T::Currency::deposit_creating(&to, minted); - Self::deposit_event(Event::Minted { to: to.clone(), amount: minted }); - if Self::on_token_minted_state() == State::Running { - weight = weight.saturating_add(T::OnTokenMinted::token_minted(to, minted)); + if let Some(id) = Self::mint_asset_id() { + if let Ok(actual) = T::Assets::mint_into(id, &to, minted) { + Self::deposit_event(Event::Minted { + asset_id: id, + to: to.clone(), + amount: actual, + }) + } + + if Self::on_token_minted_state() == State::Running { + weight = weight + .saturating_add(T::OnTokenMinted::token_minted(id, to, minted)); + } } - // 1 read: `on_token_minted_state` - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + + // 2 reads: `asset_id`, `on_token_minted_state` + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 0)); } else { // we should have minted tokens but it's forcibly stopped let skipped_blocks = @@ -269,10 +300,20 @@ pub mod pallet { /// minting #[pallet::call_index(2)] #[pallet::weight((195_000_000, DispatchClass::Normal))] - pub fn start_mint_from_next_block(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn start_mint_from_next_block( + origin: OriginFor, + id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResultWithPostInfo { Self::start_mint_from_block( origin, frame_system::Pallet::::block_number() + BlockNumberFor::::one(), + id, + name, + symbol, + decimals, ) } @@ -282,6 +323,10 @@ pub mod pallet { pub fn start_mint_from_block( origin: OriginFor, start_block: BlockNumberFor, + id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, ) -> DispatchResultWithPostInfo { T::ManagerOrigin::ensure_origin(origin)?; ensure!(StartBlock::::get().is_none(), Error::::MintAlreadyStarted); @@ -292,7 +337,10 @@ pub mod pallet { MintState::::put(State::Running); OnTokenMintedState::::put(State::Running); StartBlock::::put(start_block); - Self::deposit_event(Event::MintStarted { start_block }); + T::Assets::create(id, Self::beneficiary_account(), true, 1u32.into())?; + T::Assets::set(id, &Self::beneficiary_account(), name, symbol, decimals)?; + MintAssetId::::put(id); + Self::deposit_event(Event::MintStarted { asset_id: id, start_block }); Ok(Pays::No.into()) } } diff --git a/pallets/halving-mint/src/mock.rs b/pallets/halving-mint/src/mock.rs index 33b6ad6d29..b19efabb58 100644 --- a/pallets/halving-mint/src/mock.rs +++ b/pallets/halving-mint/src/mock.rs @@ -1,16 +1,39 @@ -use crate::{self as pallet_halving_mint, Instance1, OnTokenMinted}; +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry 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. +// +// Litentry 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 Litentry. If not, see . + +use crate::{self as pallet_halving_mint, Config, Instance1, OnTokenMinted}; use frame_support::pallet_prelude::*; -use frame_support::{construct_runtime, parameter_types, PalletId}; +use frame_support::traits::tokens::{fungibles::Mutate, Preservation}; +use frame_support::{construct_runtime, parameter_types, traits::AsEnsureOriginWithArg, PalletId}; +use frame_system::{EnsureRoot, EnsureSigned}; use sp_core::{ConstU32, ConstU64, H256}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; +type AccountId = u64; +type Balance = u64; +type AssetId = u64; + construct_runtime!( pub enum Test { System: frame_system, + Assets: pallet_assets, Balances: pallet_balances, HalvingMint: pallet_halving_mint::, } @@ -19,6 +42,7 @@ construct_runtime!( parameter_types! { pub const BlockHashCount: u32 = 250; } + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -45,6 +69,38 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } +parameter_types! { + pub const AssetDeposit: Balance = 0; + pub const AssetAccountDeposit: Balance = 0; + pub const ApprovalDeposit: Balance = 0; + pub const AssetsStringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 0; + pub const MetadataDepositPerByte: Balance = 0; +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = AssetAccountDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<0>; + type AssetIdParameter = AssetId; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + parameter_types! { pub const ExistentialDeposit: u64 = 2; pub const MaxLocks: u32 = 10; @@ -68,16 +124,19 @@ impl pallet_balances::Config for Test { parameter_types! { pub const BeneficiaryId: PalletId = PalletId(*b"lty/hlvm"); + pub const TestAssetId: AssetId = 1; } impl pallet_halving_mint::Config for Test { type RuntimeEvent = RuntimeEvent; - type Currency = Balances; + type AssetBalance = Balance; + type AssetId = AssetId; + type Assets = Assets; type ManagerOrigin = frame_system::EnsureRoot; type TotalIssuance = ConstU64<1000>; type HalvingInterval = ConstU32<10>; type BeneficiaryId = BeneficiaryId; - type OnTokenMinted = TransferOnTokenMinted; + type OnTokenMinted = TransferOnTokenMinted; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -105,14 +164,20 @@ pub fn run_to_block(n: u64) { } } -pub struct TransferOnTokenMinted(sp_std::marker::PhantomData); +pub struct TransferOnTokenMinted(sp_std::marker::PhantomData<(T, I)>); -impl OnTokenMinted for TransferOnTokenMinted +impl OnTokenMinted for TransferOnTokenMinted where - T: frame_system::Config + pallet_balances::Config, + T: frame_system::Config + Config, + T::Assets: Mutate, { - fn token_minted(beneficiary: T::AccountId, amount: T::Balance) -> Weight { - let _ = Balances::transfer(RuntimeOrigin::signed(beneficiary), 1, amount); + fn token_minted( + asset_id: T::AssetId, + beneficiary: T::AccountId, + amount: T::AssetBalance, + ) -> Weight { + let _ = T::Assets::transfer(asset_id, &beneficiary, &1, amount, Preservation::Expendable) + .unwrap(); Weight::zero() } } diff --git a/pallets/halving-mint/src/tests.rs b/pallets/halving-mint/src/tests.rs index b6d049cdb1..074534ef8b 100644 --- a/pallets/halving-mint/src/tests.rs +++ b/pallets/halving-mint/src/tests.rs @@ -1,4 +1,20 @@ -use crate::{mock::*, Error, Event, Instance1, State}; +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry 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. +// +// Litentry 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 Litentry. If not, see . + +use crate::{mock::*, Error, Event, Inspect, Instance1, State}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -13,7 +29,13 @@ fn set_mint_state_check_works() { HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), Error::::MintNotStarted, ); - assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + assert_ok!(HalvingMint::start_mint_from_next_block( + RuntimeOrigin::root(), + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + )); assert_eq!(HalvingMint::mint_state(), State::Running); assert_noop!( HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running), @@ -30,15 +52,36 @@ fn start_mint_too_early_fails() { new_test_ext().execute_with(|| { assert_eq!(System::block_number(), 1); assert_noop!( - HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 0), + HalvingMint::start_mint_from_block( + RuntimeOrigin::root(), + 0, + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + ), Error::::StartBlockTooEarly, ); assert_noop!( - HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 1), + HalvingMint::start_mint_from_block( + RuntimeOrigin::root(), + 1, + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + ), Error::::StartBlockTooEarly, ); - assert_ok!(HalvingMint::start_mint_from_block(RuntimeOrigin::root(), 2)); - System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); + assert_ok!(HalvingMint::start_mint_from_block( + RuntimeOrigin::root(), + 2, + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + )); + System::assert_last_event(Event::MintStarted { asset_id: 1, start_block: 2 }.into()); }); } @@ -48,54 +91,54 @@ fn halving_mint_works() { let beneficiary = HalvingMint::beneficiary_account(); assert_eq!(System::block_number(), 1); - assert_eq!(Balances::total_issuance(), 10); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); - System::assert_last_event(Event::MintStarted { start_block: 2 }.into()); + assert_eq!(Assets::total_issuance(1), 0); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_ok!(HalvingMint::start_mint_from_next_block( + RuntimeOrigin::root(), + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + )); + System::assert_last_event(Event::MintStarted { asset_id: 1, start_block: 2 }.into()); run_to_block(2); // 50 tokens are minted - assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Assets::total_issuance(1), 50); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_eq!(Assets::balance(1, 1), 50); run_to_block(11); - assert_eq!(Balances::total_issuance(), 510); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 500); + assert_eq!(Assets::total_issuance(1), 500); + assert_eq!(Assets::balance(1, 1), 500); run_to_block(12); // the first halving - assert_eq!(Balances::total_issuance(), 535); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 525); + assert_eq!(Assets::total_issuance(1), 525); + assert_eq!(Assets::balance(1, 1), 525); run_to_block(22); // the second halving - assert_eq!(Balances::total_issuance(), 772); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 762); + assert_eq!(Assets::total_issuance(1), 762); + assert_eq!(Assets::balance(1, 1), 762); run_to_block(52); // the fifth halving - only 1 token is minted - assert_eq!(Balances::total_issuance(), 971); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 961); + assert_eq!(Assets::total_issuance(1), 961); + assert_eq!(Assets::balance(1, 1), 961); run_to_block(62); // the sixth halving - but 0 tokens will be minted - assert_eq!(Balances::total_issuance(), 980); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 970); + assert_eq!(Assets::total_issuance(1), 970); + assert_eq!(Assets::balance(1, 1), 970); run_to_block(1_000); - // no changes since the sixth halving, the total minted token will be fixated on 980, - // the "missing" 20 comes from the integer division and the total_issuance is too small. + // no changes since the sixth halving, the total minted token will be fixated on 970, + // the "missing" 30 comes from the integer division and the total_issuance is too small. // // we'll have much accurate result in reality where token unit is 18 decimal - assert_eq!(Balances::total_issuance(), 980); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 970); + assert_eq!(Assets::total_issuance(1), 970); + assert_eq!(Assets::balance(1, 1), 970); }); } @@ -104,7 +147,13 @@ fn set_on_token_minted_state_works() { new_test_ext().execute_with(|| { let beneficiary = HalvingMint::beneficiary_account(); - assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + assert_ok!(HalvingMint::start_mint_from_next_block( + RuntimeOrigin::root(), + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + )); assert_ok!(HalvingMint::set_on_token_minted_state(RuntimeOrigin::root(), State::Stopped)); System::assert_last_event( Event::OnTokenMintedStateChanged { new_state: State::Stopped }.into(), @@ -112,9 +161,9 @@ fn set_on_token_minted_state_works() { run_to_block(2); // 50 tokens are minted, but none is transferred away - assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(beneficiary), 60); - assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Assets::total_issuance(1), 50); + assert_eq!(Assets::balance(1, beneficiary), 50); + assert_eq!(Assets::balance(1, 1), 0); run_to_block(10); assert_ok!(HalvingMint::set_on_token_minted_state(RuntimeOrigin::root(), State::Running)); @@ -124,9 +173,9 @@ fn set_on_token_minted_state_works() { run_to_block(11); // start to transfer token - assert_eq!(Balances::total_issuance(), 510); - assert_eq!(Balances::free_balance(beneficiary), 460); - assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Assets::total_issuance(1), 500); + assert_eq!(Assets::balance(1, beneficiary), 450); + assert_eq!(Assets::balance(1, 1), 50); }); } @@ -135,35 +184,41 @@ fn set_mint_state_works() { new_test_ext().execute_with(|| { let beneficiary = HalvingMint::beneficiary_account(); - assert_ok!(HalvingMint::start_mint_from_next_block(RuntimeOrigin::root())); + assert_ok!(HalvingMint::start_mint_from_next_block( + RuntimeOrigin::root(), + 1, + "Test".as_bytes().to_vec(), + "Test".as_bytes().to_vec(), + 18 + )); run_to_block(2); - assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Assets::total_issuance(1), 50); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_eq!(Assets::balance(1, 1), 50); // stop the minting assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Stopped)); run_to_block(3); // no new tokens should be minted - assert_eq!(Balances::total_issuance(), 60); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Assets::total_issuance(1), 50); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_eq!(Assets::balance(1, 1), 50); run_to_block(4); // resume the minting assert_ok!(HalvingMint::set_mint_state(RuntimeOrigin::root(), State::Running)); run_to_block(5); - assert_eq!(Balances::total_issuance(), 110); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 100); + assert_eq!(Assets::total_issuance(1), 100); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_eq!(Assets::balance(1, 1), 100); assert_eq!(HalvingMint::skipped_blocks(), 2); // the first halving should be delayed to block 14 run_to_block(14); - assert_eq!(Balances::total_issuance(), 535); - assert_eq!(Balances::free_balance(beneficiary), 10); - assert_eq!(Balances::free_balance(1), 525); + assert_eq!(Assets::total_issuance(1), 525); + assert_eq!(Assets::balance(1, beneficiary), 0); + assert_eq!(Assets::balance(1, 1), 525); }); } diff --git a/pallets/halving-mint/src/traits.rs b/pallets/halving-mint/src/traits.rs index 691b363a12..19bfdcebf7 100644 --- a/pallets/halving-mint/src/traits.rs +++ b/pallets/halving-mint/src/traits.rs @@ -1,12 +1,28 @@ +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry 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. +// +// Litentry 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 Litentry. If not, see . + /// Traits for pallet-halving-mint use frame_support::pallet_prelude::Weight; -pub trait OnTokenMinted { - fn token_minted(beneficiary: AccountId, amount: Balance) -> Weight; +pub trait OnTokenMinted { + fn token_minted(asset_id: AssetId, beneficiary: AccountId, amount: Balance) -> Weight; } -impl OnTokenMinted for () { - fn token_minted(_beneficiary: AccountId, _amount: Balance) -> Weight { +impl OnTokenMinted for () { + fn token_minted(_asset_id: AssetId, _beneficiary: AccountId, _amount: Balance) -> Weight { Weight::zero() } } From 9d2afeb55e0eff71fa1308dc84a1117ab0c5ce48 Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Thu, 26 Sep 2024 10:26:14 +0000 Subject: [PATCH 5/5] move pallets --- {pallets => parachain/pallets}/halving-mint/Cargo.toml | 0 {pallets => parachain/pallets}/halving-mint/src/lib.rs | 0 {pallets => parachain/pallets}/halving-mint/src/mock.rs | 0 {pallets => parachain/pallets}/halving-mint/src/tests.rs | 0 {pallets => parachain/pallets}/halving-mint/src/traits.rs | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {pallets => parachain/pallets}/halving-mint/Cargo.toml (100%) rename {pallets => parachain/pallets}/halving-mint/src/lib.rs (100%) rename {pallets => parachain/pallets}/halving-mint/src/mock.rs (100%) rename {pallets => parachain/pallets}/halving-mint/src/tests.rs (100%) rename {pallets => parachain/pallets}/halving-mint/src/traits.rs (100%) diff --git a/pallets/halving-mint/Cargo.toml b/parachain/pallets/halving-mint/Cargo.toml similarity index 100% rename from pallets/halving-mint/Cargo.toml rename to parachain/pallets/halving-mint/Cargo.toml diff --git a/pallets/halving-mint/src/lib.rs b/parachain/pallets/halving-mint/src/lib.rs similarity index 100% rename from pallets/halving-mint/src/lib.rs rename to parachain/pallets/halving-mint/src/lib.rs diff --git a/pallets/halving-mint/src/mock.rs b/parachain/pallets/halving-mint/src/mock.rs similarity index 100% rename from pallets/halving-mint/src/mock.rs rename to parachain/pallets/halving-mint/src/mock.rs diff --git a/pallets/halving-mint/src/tests.rs b/parachain/pallets/halving-mint/src/tests.rs similarity index 100% rename from pallets/halving-mint/src/tests.rs rename to parachain/pallets/halving-mint/src/tests.rs diff --git a/pallets/halving-mint/src/traits.rs b/parachain/pallets/halving-mint/src/traits.rs similarity index 100% rename from pallets/halving-mint/src/traits.rs rename to parachain/pallets/halving-mint/src/traits.rs