From fdfce9e207aa2fee4e3609c56ad06b3fbd320942 Mon Sep 17 00:00:00 2001 From: akildemir <34187742+akildemir@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:22:21 +0300 Subject: [PATCH] Coins pallet (#399) * initial implementation * add function to get a balance of an account * add support for multiple coins * rename pallet to "coins-pallet" * replace balances, assets and tokens pallet with coins pallet in runtime * add total supply info * update client side for new Coins pallet * handle fees * bug fixes * Update FeeAccount test * Fmt * fix pr comments * remove extraneous Imbalance type * Minor tweaks --------- Co-authored-by: Luke Parker --- .github/workflows/tests.yml | 4 +- Cargo.lock | 95 +++--- Cargo.toml | 4 +- coordinator/src/substrate/mod.rs | 8 +- deny.toml | 2 +- processor/messages/Cargo.toml | 2 +- processor/messages/src/lib.rs | 2 +- substrate/client/src/lib.rs | 2 +- substrate/client/src/serai/coins.rs | 66 ++--- substrate/client/tests/batch.rs | 8 +- substrate/client/tests/burn.rs | 22 +- substrate/coins/pallet/Cargo.toml | 49 +++ substrate/{tokens => coins}/pallet/LICENSE | 0 substrate/coins/pallet/src/lib.rs | 278 ++++++++++++++++++ .../{tokens => coins}/primitives/Cargo.toml | 4 +- .../{tokens => coins}/primitives/LICENSE | 0 .../{tokens => coins}/primitives/src/lib.rs | 9 +- substrate/in-instructions/pallet/Cargo.toml | 4 +- substrate/in-instructions/pallet/src/lib.rs | 9 +- .../in-instructions/primitives/Cargo.toml | 4 +- .../primitives/src/shorthand.rs | 2 +- substrate/node/src/chain_spec.rs | 25 +- substrate/primitives/src/account.rs | 2 +- substrate/primitives/src/networks.rs | 33 +++ substrate/runtime/Cargo.toml | 15 +- substrate/runtime/src/lib.rs | 107 +------ substrate/staking/pallet/Cargo.toml | 18 +- substrate/staking/pallet/src/lib.rs | 22 +- substrate/tokens/pallet/Cargo.toml | 38 --- substrate/tokens/pallet/src/lib.rs | 82 ------ tests/coordinator/src/tests/sign.rs | 47 ++- tests/full-stack/src/tests/mint_and_burn.rs | 17 +- 32 files changed, 535 insertions(+), 445 deletions(-) create mode 100644 substrate/coins/pallet/Cargo.toml rename substrate/{tokens => coins}/pallet/LICENSE (100%) create mode 100644 substrate/coins/pallet/src/lib.rs rename substrate/{tokens => coins}/primitives/Cargo.toml (92%) rename substrate/{tokens => coins}/primitives/LICENSE (100%) rename substrate/{tokens => coins}/primitives/src/lib.rs (84%) delete mode 100644 substrate/tokens/pallet/Cargo.toml delete mode 100644 substrate/tokens/pallet/src/lib.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b888b337..38d84c98 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,8 +60,8 @@ jobs: run: | GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \ -p serai-primitives \ - -p serai-tokens-primitives \ - -p serai-tokens-pallet \ + -p serai-coins-primitives \ + -p serai-coins-pallet \ -p serai-in-instructions-primitives \ -p serai-in-instructions-pallet \ -p serai-validator-sets-primitives \ diff --git a/Cargo.lock b/Cargo.lock index f457d803..e25f0d97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5488,21 +5488,6 @@ dependencies = [ "group", ] -[[package]] -name = "pallet-assets" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#98ab693fdf71f371d5059aa6924a410c8bb0a675" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" @@ -5557,21 +5542,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-balances" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#98ab693fdf71f371d5059aa6924a410c8bb0a675" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-grandpa" version = "4.0.0-dev" @@ -8221,6 +8191,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "serai-coins-pallet" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "serai-coins-primitives", + "serai-primitives", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "serai-coins-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serai-primitives", + "serde", + "sp-runtime", + "zeroize", +] + [[package]] name = "serai-coordinator" version = "0.1.0" @@ -8325,9 +8323,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", + "serai-coins-pallet", "serai-in-instructions-primitives", "serai-primitives", - "serai-tokens-pallet", "serai-validator-sets-pallet", "sp-application-crypto", "sp-core", @@ -8342,8 +8340,8 @@ version = "0.1.0" dependencies = [ "parity-scale-codec", "scale-info", + "serai-coins-primitives", "serai-primitives", - "serai-tokens-primitives", "serde", "sp-application-crypto", "sp-runtime", @@ -8503,9 +8501,9 @@ version = "0.1.0" dependencies = [ "dkg", "parity-scale-codec", + "serai-coins-primitives", "serai-in-instructions-primitives", "serai-primitives", - "serai-tokens-primitives", "serai-validator-sets-primitives", "serde", "zeroize", @@ -8556,10 +8554,8 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", - "pallet-assets", "pallet-authority-discovery", "pallet-babe", - "pallet-balances", "pallet-grandpa", "pallet-session", "pallet-timestamp", @@ -8567,10 +8563,10 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", + "serai-coins-pallet", "serai-in-instructions-pallet", "serai-primitives", "serai-staking-pallet", - "serai-tokens-pallet", "serai-validator-sets-pallet", "sp-api", "sp-authority-discovery", @@ -8597,38 +8593,13 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", + "serai-coins-pallet", "serai-primitives", "serai-validator-sets-pallet", - "serai-validator-sets-primitives", "sp-runtime", "sp-std", ] -[[package]] -name = "serai-tokens-pallet" -version = "0.1.0" -dependencies = [ - "frame-support", - "frame-system", - "pallet-assets", - "parity-scale-codec", - "scale-info", - "serai-primitives", - "serai-tokens-primitives", -] - -[[package]] -name = "serai-tokens-primitives" -version = "0.1.0" -dependencies = [ - "parity-scale-codec", - "scale-info", - "serai-primitives", - "serde", - "sp-runtime", - "zeroize", -] - [[package]] name = "serai-validator-sets-pallet" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 41ec781f..c329fe08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,8 @@ members = [ "substrate/primitives", - "substrate/tokens/primitives", - "substrate/tokens/pallet", + "substrate/coins/primitives", + "substrate/coins/pallet", "substrate/in-instructions/primitives", "substrate/in-instructions/pallet", diff --git a/coordinator/src/substrate/mod.rs b/coordinator/src/substrate/mod.rs index d25125a9..20345c61 100644 --- a/coordinator/src/substrate/mod.rs +++ b/coordinator/src/substrate/mod.rs @@ -16,7 +16,7 @@ use serai_client::{ ValidatorSetsEvent, }, in_instructions::InInstructionsEvent, - coins::{primitives::OutInstructionWithBalance, TokensEvent}, + coins::CoinsEvent, }; use serai_db::DbTxn; @@ -209,12 +209,12 @@ async fn handle_batch_and_burns( } for burn in serai.coins().burn_events().await? { - if let TokensEvent::Burn { address: _, balance, instruction } = burn { - let network = balance.coin.network(); + if let CoinsEvent::Burn { address: _, instruction } = burn { + let network = instruction.balance.coin.network(); network_had_event(&mut burns, &mut batches, network); // network_had_event should register an entry in burns - burns.get_mut(&network).unwrap().push(OutInstructionWithBalance { balance, instruction }); + burns.get_mut(&network).unwrap().push(instruction); } else { panic!("Burn event wasn't Burn: {burn:?}"); } diff --git a/deny.toml b/deny.toml index b02e4c87..701a0e1d 100644 --- a/deny.toml +++ b/deny.toml @@ -54,7 +54,7 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "tributary-chain" }, { allow = ["AGPL-3.0"], name = "serai-coordinator" }, - { allow = ["AGPL-3.0"], name = "serai-tokens-pallet" }, + { allow = ["AGPL-3.0"], name = "serai-coins-pallet" }, { allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" }, diff --git a/processor/messages/Cargo.toml b/processor/messages/Cargo.toml index 470883fc..b4078f90 100644 --- a/processor/messages/Cargo.toml +++ b/processor/messages/Cargo.toml @@ -23,5 +23,5 @@ dkg = { path = "../../crypto/dkg", features = ["serde"] } serai-primitives = { path = "../../substrate/primitives" } in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../../substrate/in-instructions/primitives" } -tokens-primitives = { package = "serai-tokens-primitives", path = "../../substrate/tokens/primitives" } +coins-primitives = { package = "serai-coins-primitives", path = "../../substrate/coins/primitives" } validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../substrate/validator-sets/primitives" } diff --git a/processor/messages/src/lib.rs b/processor/messages/src/lib.rs index dbb139c0..006940f6 100644 --- a/processor/messages/src/lib.rs +++ b/processor/messages/src/lib.rs @@ -9,7 +9,7 @@ use dkg::{Participant, ThresholdParams}; use serai_primitives::{BlockHash, NetworkId}; use in_instructions_primitives::{Batch, SignedBatch}; -use tokens_primitives::OutInstructionWithBalance; +use coins_primitives::OutInstructionWithBalance; use validator_sets_primitives::{ValidatorSet, KeyPair}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)] diff --git a/substrate/client/src/lib.rs b/substrate/client/src/lib.rs index 4949c0a0..3338d323 100644 --- a/substrate/client/src/lib.rs +++ b/substrate/client/src/lib.rs @@ -15,7 +15,7 @@ mod other_primitives { pub use serai_runtime::in_instructions::primitives; } pub mod coins { - pub use serai_runtime::tokens::primitives; + pub use serai_runtime::coins::primitives; } pub mod validator_sets { pub use serai_runtime::validator_sets::primitives; diff --git a/substrate/client/src/serai/coins.rs b/substrate/client/src/serai/coins.rs index 1d6d255a..2ab1d945 100644 --- a/substrate/client/src/serai/coins.rs +++ b/substrate/client/src/serai/coins.rs @@ -1,19 +1,17 @@ -use sp_core::sr25519::Public; use serai_runtime::{ primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance}, - assets::{AssetDetails, AssetAccount}, - tokens, Tokens, Runtime, + coins, Coins, Runtime, }; -pub use tokens::primitives; -use primitives::OutInstruction; +pub use coins::primitives; +use primitives::OutInstructionWithBalance; use subxt::tx::Payload; use crate::{TemporalSerai, SeraiError, Composite, scale_value, scale_composite}; -const PALLET: &str = "Tokens"; +const PALLET: &str = "Coins"; -pub type TokensEvent = tokens::Event; +pub type CoinsEvent = coins::Event; #[derive(Clone, Copy)] pub struct SeraiCoins<'a>(pub(crate) TemporalSerai<'a>); @@ -22,37 +20,25 @@ impl<'a> SeraiCoins<'a> { self.0 } - pub async fn mint_events(&self) -> Result, SeraiError> { - self.0.events::(|event| matches!(event, TokensEvent::Mint { .. })).await + pub async fn mint_events(&self) -> Result, SeraiError> { + self.0.events::(|event| matches!(event, CoinsEvent::Mint { .. })).await } - pub async fn burn_events(&self) -> Result, SeraiError> { - self.0.events::(|event| matches!(event, TokensEvent::Burn { .. })).await + pub async fn burn_events(&self) -> Result, SeraiError> { + self.0.events::(|event| matches!(event, CoinsEvent::Burn { .. })).await } - pub async fn sri_balance(&self, address: SeraiAddress) -> Result { - let data: Option< - serai_runtime::system::AccountInfo>, - > = self.0.storage("System", "Account", Some(vec![scale_value(address)])).await?; - Ok(data.map(|data| data.data.free).unwrap_or(0)) - } - - pub async fn token_supply(&self, coin: Coin) -> Result { + pub async fn coin_supply(&self, coin: Coin) -> Result { Ok(Amount( self .0 - .storage::>( - "Assets", - "Asset", - Some(vec![scale_value(coin)]), - ) + .storage::(PALLET, "Supply", Some(vec![scale_value(coin)])) .await? - .map(|token| token.supply) .unwrap_or(0), )) } - pub async fn token_balance( + pub async fn coin_balance( &self, coin: Coin, address: SeraiAddress, @@ -60,35 +46,25 @@ impl<'a> SeraiCoins<'a> { Ok(Amount( self .0 - .storage::>( - "Assets", - "Account", - Some(vec![scale_value(coin), scale_value(address)]), + .storage::( + PALLET, + "Balances", + Some(vec![scale_value(address), scale_value(coin)]), ) .await? - .map(|account| account.balance()) .unwrap_or(0), )) } - pub fn transfer_sri(to: SeraiAddress, amount: Amount) -> Payload> { + pub fn transfer(to: SeraiAddress, balance: Balance) -> Payload> { Payload::new( - "Balances", - // TODO: Use transfer_allow_death? - // TODO: Replace the Balances pallet with something much simpler + PALLET, "transfer", - scale_composite(serai_runtime::balances::Call::::transfer { - dest: to, - value: amount.0, - }), + scale_composite(serai_runtime::coins::Call::::transfer { to, balance }), ) } - pub fn burn(balance: Balance, instruction: OutInstruction) -> Payload> { - Payload::new( - PALLET, - "burn", - scale_composite(tokens::Call::::burn { balance, instruction }), - ) + pub fn burn(instruction: OutInstructionWithBalance) -> Payload> { + Payload::new(PALLET, "burn", scale_composite(coins::Call::::burn { instruction })) } } diff --git a/substrate/client/tests/batch.rs b/substrate/client/tests/batch.rs index 2b747747..e90f6855 100644 --- a/substrate/client/tests/batch.rs +++ b/substrate/client/tests/batch.rs @@ -13,7 +13,7 @@ use serai_client::{ primitives::{InInstruction, InInstructionWithBalance, Batch}, InInstructionsEvent, }, - coins::TokensEvent, + coins::CoinsEvent, Serai, }; @@ -65,8 +65,8 @@ serai_test!( } let serai = serai.coins(); - assert_eq!(serai.mint_events().await.unwrap(), vec![TokensEvent::Mint { address, balance }],); - assert_eq!(serai.token_supply(coin).await.unwrap(), amount); - assert_eq!(serai.token_balance(coin, address).await.unwrap(), amount); + assert_eq!(serai.mint_events().await.unwrap(), vec![CoinsEvent::Mint { address, balance }],); + assert_eq!(serai.coin_supply(coin).await.unwrap(), amount); + assert_eq!(serai.coin_balance(coin, address).await.unwrap(), amount); } ); diff --git a/substrate/client/tests/burn.rs b/substrate/client/tests/burn.rs index 2a03e566..2e1800ba 100644 --- a/substrate/client/tests/burn.rs +++ b/substrate/client/tests/burn.rs @@ -7,6 +7,7 @@ use blake2::{ use scale::Encode; +use serai_runtime::coins::primitives::OutInstructionWithBalance; use sp_core::Pair; use serai_client::{ @@ -19,7 +20,7 @@ use serai_client::{ InInstructionsEvent, primitives::{InInstruction, InInstructionWithBalance, Batch}, }, - coins::{primitives::OutInstruction, TokensEvent}, + coins::{primitives::OutInstruction, CoinsEvent}, PairSigner, Serai, SeraiCoins, }; @@ -69,10 +70,10 @@ serai_test!( assert_eq!( serai.coins().mint_events().await.unwrap(), - vec![TokensEvent::Mint { address, balance }] + vec![CoinsEvent::Mint { address, balance }] ); - assert_eq!(serai.coins().token_supply(coin).await.unwrap(), amount); - assert_eq!(serai.coins().token_balance(coin, address).await.unwrap(), amount); + assert_eq!(serai.coins().coin_supply(coin).await.unwrap(), amount); + assert_eq!(serai.coins().coin_balance(coin, address).await.unwrap(), amount); // Now burn it let mut rand_bytes = vec![0; 32]; @@ -83,13 +84,16 @@ serai_test!( OsRng.fill_bytes(&mut rand_bytes); let data = Data::new(rand_bytes).unwrap(); - let out = OutInstruction { address: external_address, data: Some(data) }; + let instruction = OutInstructionWithBalance { + balance, + instruction: OutInstruction { address: external_address, data: Some(data) }, + }; let serai = serai.into_inner(); let block = publish_tx( &serai .sign( &PairSigner::new(pair), - &SeraiCoins::burn(balance, out.clone()), + &SeraiCoins::burn(instruction.clone()), 0, BaseExtrinsicParamsBuilder::new(), ) @@ -99,8 +103,8 @@ serai_test!( let serai = serai.as_of(block).coins(); let events = serai.burn_events().await.unwrap(); - assert_eq!(events, vec![TokensEvent::Burn { address, balance, instruction: out }]); - assert_eq!(serai.token_supply(coin).await.unwrap(), Amount(0)); - assert_eq!(serai.token_balance(coin, address).await.unwrap(), Amount(0)); + assert_eq!(events, vec![CoinsEvent::Burn { address, instruction }]); + assert_eq!(serai.coin_supply(coin).await.unwrap(), Amount(0)); + assert_eq!(serai.coin_balance(coin, address).await.unwrap(), Amount(0)); } ); diff --git a/substrate/coins/pallet/Cargo.toml b/substrate/coins/pallet/Cargo.toml new file mode 100644 index 00000000..2ee8ecac --- /dev/null +++ b/substrate/coins/pallet/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "serai-coins-pallet" +version = "0.1.0" +description = "Coins pallet for Serai" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/coins/pallet" +authors = ["Akil Demir "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +parity-scale-codec = { version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } + +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } + +pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } + +serai-primitives = { path = "../../primitives", default-features = false } +coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false } + +[features] +std = [ + "frame-system/std", + "frame-support/std", + + "sp-std/std", + "sp-runtime/std", + + "pallet-transaction-payment/std", + + "serai-primitives/std", + "coins-primitives/std", +] + +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] + +default = ["std"] diff --git a/substrate/tokens/pallet/LICENSE b/substrate/coins/pallet/LICENSE similarity index 100% rename from substrate/tokens/pallet/LICENSE rename to substrate/coins/pallet/LICENSE diff --git a/substrate/coins/pallet/src/lib.rs b/substrate/coins/pallet/src/lib.rs new file mode 100644 index 00000000..c672159f --- /dev/null +++ b/substrate/coins/pallet/src/lib.rs @@ -0,0 +1,278 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[frame_support::pallet] +pub mod pallet { + use sp_std::vec::Vec; + use sp_core::sr25519::Public; + use sp_runtime::{ + traits::{DispatchInfoOf, PostDispatchInfoOf}, + transaction_validity::{TransactionValidityError, InvalidTransaction}, + }; + + use frame_system::pallet_prelude::*; + use frame_support::pallet_prelude::*; + + use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction}; + + use serai_primitives::*; + pub use coins_primitives as primitives; + use primitives::*; + + #[pallet::config] + pub trait Config: frame_system::Config + TpConfig { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::genesis_config] + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] + pub struct GenesisConfig { + _config: PhantomData, + pub accounts: Vec<(Public, Balance)>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { _config: PhantomData, accounts: Default::default() } + } + } + + #[pallet::error] + pub enum Error { + AmountOverflowed, + NotEnoughCoins, + SriBurnNotAllowed, + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + Mint { to: Public, balance: Balance }, + Burn { from: Public, instruction: OutInstructionWithBalance }, + SriBurn { from: Public, amount: Amount }, + Transfer { from: Public, to: Public, balance: Balance }, + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// The amount of coins each account has. + // Identity is used as the second key's hasher due to it being a non-manipulatable fixed-space + // ID. + #[pallet::storage] + #[pallet::getter(fn balances)] + pub type Balances = StorageDoubleMap< + _, + Blake2_128Concat, + Public, + Identity, + Coin, + SubstrateAmount, + ValueQuery, + >; + + /// The total supply of each coin. + // We use Identity type here again due to reasons stated in the Balances Storage. + #[pallet::storage] + #[pallet::getter(fn supply)] + pub type Supply = StorageMap<_, Identity, Coin, SubstrateAmount, ValueQuery>; + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + // initialize the supply of the coins + for c in COINS.iter() { + Supply::::set(c, 0); + } + + // initialize the genesis accounts + for (account, balance) in self.accounts.iter() { + Pallet::::mint(*account, *balance).unwrap(); + } + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_: BlockNumberFor) -> Weight { + // burn the fees collected previous block + let coin = Coin::Serai; + let amount = Self::balance(FEE_ACCOUNT.into(), coin); + // we can unwrap, we are not burning more then what we have + // If this errors, it'll halt the runtime however (due to being called at the start of every + // block), requiring extra care when reviewing + Self::burn_sri(FEE_ACCOUNT.into(), amount).unwrap(); + Weight::zero() // TODO + } + } + + impl Pallet { + /// Returns the balance of a given account for `coin`. + pub fn balance(of: Public, coin: Coin) -> Amount { + Amount(Self::balances(of, coin)) + } + + fn decrease_balance_internal(from: Public, balance: Balance) -> Result<(), Error> { + let coin = &balance.coin; + + // sub amount from account + let new_amount = Self::balances(from, coin) + .checked_sub(balance.amount.0) + .ok_or(Error::::NotEnoughCoins)?; + + // save + if new_amount == 0 { + Balances::::remove(from, coin); + } else { + Balances::::set(from, coin, new_amount); + } + Ok(()) + } + + fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error> { + let coin = &balance.coin; + + // sub amount from account + let new_amount = Self::balances(to, coin) + .checked_add(balance.amount.0) + .ok_or(Error::::AmountOverflowed)?; + + // save + Balances::::set(to, coin, new_amount); + Ok(()) + } + + /// Mint `balance` to the given account. + /// + /// Errors if any amount overflows. + pub fn mint(to: Public, balance: Balance) -> Result<(), Error> { + // update the balance + Self::increase_balance_internal(to, balance)?; + + // update the supply + let new_supply = Self::supply(balance.coin) + .checked_add(balance.amount.0) + .ok_or(Error::::AmountOverflowed)?; + Supply::::set(balance.coin, new_supply); + + Self::deposit_event(Event::Mint { to, balance }); + Ok(()) + } + + // Burn `balance` from the specified account. + fn burn_internal( + from: Public, + balance: Balance, + ) -> Result<(), Error> { + // don't waste time if amount == 0 + if balance.amount.0 == 0 { + return Ok(()); + } + + // update the balance + Self::decrease_balance_internal(from, balance)?; + + // update the supply + let new_supply = Self::supply(balance.coin) + .checked_sub(balance.amount.0) + .unwrap(); + Supply::::set(balance.coin, new_supply); + + Ok(()) + } + + pub fn burn_sri( + from: Public, + amount: Amount, + ) -> Result<(), Error> { + Self::burn_internal(from, Balance { coin: Coin::Serai, amount })?; + Self::deposit_event(Event::SriBurn { from, amount }); + Ok(()) + } + + pub fn burn_non_sri( + from: Public, + instruction: OutInstructionWithBalance, + ) -> Result<(), Error> { + if instruction.balance.coin == Coin::Serai { + Err(Error::::SriBurnNotAllowed)?; + } + Self::burn_internal(from, instruction.balance)?; + Self::deposit_event(Event::Burn { from, instruction }); + Ok(()) + } + + /// Transfer `balance` from `from` to `to`. + pub fn transfer_internal( + from: Public, + to: Public, + balance: Balance, + ) -> Result<(), Error> { + // update balances of accounts + Self::decrease_balance_internal(from, balance)?; + Self::increase_balance_internal(to, balance)?; + Self::deposit_event(Event::Transfer { from, to, balance }); + Ok(()) + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight((0, DispatchClass::Normal))] // TODO + pub fn transfer(origin: OriginFor, to: Public, balance: Balance) -> DispatchResult { + let from = ensure_signed(origin)?; + Self::transfer_internal(from, to, balance)?; + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight((0, DispatchClass::Normal))] // TODO + pub fn burn(origin: OriginFor, instruction: OutInstructionWithBalance) -> DispatchResult { + let from = ensure_signed(origin)?; + Self::burn_non_sri(from, instruction)?; + Ok(()) + } + } + + impl OnChargeTransaction for Pallet { + type Balance = SubstrateAmount; + type LiquidityInfo = Option; + + fn withdraw_fee( + who: &Public, + _call: &T::RuntimeCall, + _dispatch_info: &DispatchInfoOf, + fee: Self::Balance, + _tip: Self::Balance, + ) -> Result { + if fee == 0 { + return Ok(None); + } + + let balance = Balance { coin: Coin::Serai, amount: Amount(fee) }; + match Self::transfer_internal(*who, FEE_ACCOUNT.into(), balance) { + Err(_) => Err(InvalidTransaction::Payment)?, + Ok(()) => Ok(Some(fee)), + } + } + + fn correct_and_deposit_fee( + who: &Public, + _dispatch_info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + _tip: Self::Balance, + already_withdrawn: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError> { + if let Some(paid) = already_withdrawn { + let refund_amount = paid.saturating_sub(corrected_fee); + let balance = Balance { coin: Coin::Serai, amount: Amount(refund_amount) }; + Self::transfer_internal(FEE_ACCOUNT.into(), *who, balance) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; + } + Ok(()) + } + } +} + +pub use pallet::*; diff --git a/substrate/tokens/primitives/Cargo.toml b/substrate/coins/primitives/Cargo.toml similarity index 92% rename from substrate/tokens/primitives/Cargo.toml rename to substrate/coins/primitives/Cargo.toml index 29a8d9f1..302d4434 100644 --- a/substrate/tokens/primitives/Cargo.toml +++ b/substrate/coins/primitives/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "serai-tokens-primitives" +name = "serai-coins-primitives" version = "0.1.0" -description = "Serai tokens primitives" +description = "Serai coins primitives" license = "MIT" authors = ["Luke Parker "] edition = "2021" diff --git a/substrate/tokens/primitives/LICENSE b/substrate/coins/primitives/LICENSE similarity index 100% rename from substrate/tokens/primitives/LICENSE rename to substrate/coins/primitives/LICENSE diff --git a/substrate/tokens/primitives/src/lib.rs b/substrate/coins/primitives/src/lib.rs similarity index 84% rename from substrate/tokens/primitives/src/lib.rs rename to substrate/coins/primitives/src/lib.rs index 7e8420dc..911a4b6f 100644 --- a/substrate/tokens/primitives/src/lib.rs +++ b/substrate/coins/primitives/src/lib.rs @@ -10,9 +10,9 @@ use serde::{Serialize, Deserialize}; use scale::{Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; -use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, pallet_address}; +use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address}; -pub const ADDRESS: SeraiAddress = pallet_address(b"Tokens"); +pub const FEE_ACCOUNT: SeraiAddress = system_address(b"FeeAccount"); #[derive( Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo, @@ -44,5 +44,8 @@ pub enum Destination { #[test] fn address() { use sp_runtime::traits::TrailingZeroInput; - assert_eq!(ADDRESS, SeraiAddress::decode(&mut TrailingZeroInput::new(b"Tokens")).unwrap()); + assert_eq!( + FEE_ACCOUNT, + SeraiAddress::decode(&mut TrailingZeroInput::new(b"FeeAccount")).unwrap() + ); } diff --git a/substrate/in-instructions/pallet/Cargo.toml b/substrate/in-instructions/pallet/Cargo.toml index fbaf8001..c662c39e 100644 --- a/substrate/in-instructions/pallet/Cargo.toml +++ b/substrate/in-instructions/pallet/Cargo.toml @@ -28,7 +28,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur serai-primitives = { path = "../../primitives", default-features = false } in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false } -tokens-pallet = { package = "serai-tokens-pallet", path = "../../tokens/pallet", default-features = false } +coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } [features] @@ -49,7 +49,7 @@ std = [ "serai-primitives/std", "in-instructions-primitives/std", - "tokens-pallet/std", + "coins-pallet/std", "validator-sets-pallet/std", ] default = ["std"] diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index d7c67e45..acf2b4f3 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -30,7 +30,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use tokens_pallet::{Config as TokensConfig, Pallet as Tokens}; + use coins_pallet::{Config as CoinsConfig, Pallet as Coins}; use validator_sets_pallet::{ primitives::{Session, ValidatorSet}, Config as ValidatorSetsConfig, Pallet as ValidatorSets, @@ -39,7 +39,7 @@ pub mod pallet { use super::*; #[pallet::config] - pub trait Config: frame_system::Config + ValidatorSetsConfig + TokensConfig { + pub trait Config: frame_system::Config + ValidatorSetsConfig + CoinsConfig { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -73,10 +73,11 @@ pub mod pallet { impl Pallet { fn execute(instruction: InInstructionWithBalance) -> Result<(), ()> { match instruction.instruction { - InInstruction::Transfer(address) => Tokens::::mint(address, instruction.balance), + InInstruction::Transfer(address) => { + Coins::::mint(&address.into(), instruction.balance).map_err(|_| ()) + } _ => panic!("unsupported instruction"), } - Ok(()) } } diff --git a/substrate/in-instructions/primitives/Cargo.toml b/substrate/in-instructions/primitives/Cargo.toml index 72f962dc..789b1bb5 100644 --- a/substrate/in-instructions/primitives/Cargo.toml +++ b/substrate/in-instructions/primitives/Cargo.toml @@ -23,7 +23,7 @@ sp-std = { git = "https://github.com/serai-dex/substrate", default-features = fa sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } serai-primitives = { path = "../../primitives", default-features = false } -tokens-primitives = { package = "serai-tokens-primitives", path = "../../tokens/primitives", default-features = false } +coins-primitives = { package = "serai-coins-primitives", path = "../../coins/primitives", default-features = false } [features] std = [ @@ -38,6 +38,6 @@ std = [ "sp-runtime/std", "serai-primitives/std", - "tokens-primitives/std" + "coins-primitives/std" ] default = ["std"] diff --git a/substrate/in-instructions/primitives/src/shorthand.rs b/substrate/in-instructions/primitives/src/shorthand.rs index a382d052..8f415add 100644 --- a/substrate/in-instructions/primitives/src/shorthand.rs +++ b/substrate/in-instructions/primitives/src/shorthand.rs @@ -8,7 +8,7 @@ use scale_info::TypeInfo; use serai_primitives::{Coin, Amount, SeraiAddress, ExternalAddress}; -use tokens_primitives::OutInstruction; +use coins_primitives::OutInstruction; use crate::RefundableInInstruction; #[cfg(feature = "std")] diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 40a3f555..6941296f 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -5,9 +5,9 @@ use sp_core::Pair as PairTrait; use sc_service::ChainType; use serai_runtime::{ - primitives::*, tokens::primitives::ADDRESS as TOKENS_ADDRESS, WASM_BINARY, opaque::SessionKeys, - BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, BalancesConfig, AssetsConfig, - ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig, AuthorityDiscoveryConfig, + primitives::*, WASM_BINARY, opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, + SystemConfig, ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig, + AuthorityDiscoveryConfig, CoinsConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -34,24 +34,13 @@ fn testnet_genesis( RuntimeGenesisConfig { system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData }, - balances: BalancesConfig { - balances: endowed_accounts.into_iter().map(|k| (k, 1 << 60)).collect(), - }, transaction_payment: Default::default(), - assets: AssetsConfig { - assets: [Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero] - .iter() - .map(|coin| (*coin, TOKENS_ADDRESS.into(), true, 1)) + coins: CoinsConfig { + accounts: endowed_accounts + .into_iter() + .map(|a| (a, Balance { coin: Coin::Serai, amount: Amount(1 << 60) })) .collect(), - metadata: vec![ - (Coin::Bitcoin, b"Bitcoin".to_vec(), b"BTC".to_vec(), 8), - // Reduce to 8 decimals to feasibly fit within u64 (instead of its native u256) - (Coin::Ether, b"Ether".to_vec(), b"ETH".to_vec(), 8), - (Coin::Dai, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8), - (Coin::Monero, b"Monero".to_vec(), b"XMR".to_vec(), 12), - ], - accounts: vec![], }, validator_sets: ValidatorSetsConfig { diff --git a/substrate/primitives/src/account.rs b/substrate/primitives/src/account.rs index fd4c7ffe..094e17f9 100644 --- a/substrate/primitives/src/account.rs +++ b/substrate/primitives/src/account.rs @@ -88,7 +88,7 @@ impl StaticLookup for AccountLookup { } } -pub const fn pallet_address(pallet: &'static [u8]) -> SeraiAddress { +pub const fn system_address(pallet: &'static [u8]) -> SeraiAddress { let mut address = [0; 32]; let mut set = false; // Implement a while loop since we can't use a for loop diff --git a/substrate/primitives/src/networks.rs b/substrate/primitives/src/networks.rs index 22c32ebe..608217b8 100644 --- a/substrate/primitives/src/networks.rs +++ b/substrate/primitives/src/networks.rs @@ -34,6 +34,8 @@ pub enum NetworkId { pub const NETWORKS: [NetworkId; 4] = [NetworkId::Serai, NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero]; +pub const COINS: [Coin; 5] = [Coin::Serai, Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero]; + /// The type used to identify coins. #[derive( Clone, @@ -68,6 +70,37 @@ impl Coin { Coin::Monero => NetworkId::Monero, } } + + pub fn name(&self) -> &'static str { + match self { + Coin::Serai => "Serai", + Coin::Bitcoin => "Bitcoin", + Coin::Ether => "Ether", + Coin::Dai => "Dai Stablecoin", + Coin::Monero => "Monero", + } + } + + pub fn symbol(&self) -> &'static str { + match self { + Coin::Serai => "SRI", + Coin::Bitcoin => "BTC", + Coin::Ether => "ETH", + Coin::Dai => "DAI", + Coin::Monero => "XMR", + } + } + + pub fn decimals(&self) -> u32 { + match self { + Coin::Serai => 8, + Coin::Bitcoin => 8, + // Ether and DAI have 18 decimals, yet we only track 8 in order to fit them within u64s + Coin::Ether => 8, + Coin::Dai => 8, + Coin::Monero => 12, + } + } } // Max of 8 coins per network diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 772918d3..9fd44ff1 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -43,11 +43,9 @@ serai-primitives = { path = "../primitives", default-features = false } pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-balances = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } -tokens-pallet = { package = "serai-tokens-pallet", path = "../tokens/pallet", default-features = false } +coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false } in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false } staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false } @@ -97,11 +95,9 @@ std = [ "pallet-timestamp/std", - "pallet-balances/std", "pallet-transaction-payment/std", - "pallet-assets/std", - "tokens-pallet/std", + "coins-pallet/std", "in-instructions-pallet/std", "staking-pallet/std", @@ -126,11 +122,8 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - - "pallet-babe/runtime-benchmarks", - "pallet-grandpa/runtime-benchmarks", + "pallet-babe/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", ] default = ["std"] diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 579f8388..f9b996fa 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -14,11 +14,9 @@ pub use frame_support as support; pub use pallet_timestamp as timestamp; -pub use pallet_balances as balances; pub use pallet_transaction_payment as transaction_payment; -pub use pallet_assets as assets; -pub use tokens_pallet as tokens; +pub use coins_pallet as coins; pub use in_instructions_pallet as in_instructions; pub use staking_pallet as staking; @@ -45,10 +43,10 @@ use sp_runtime::{ ApplyExtrinsicResult, Perbill, }; -use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount, Coin}; +use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount}; use support::{ - traits::{ConstU8, ConstU32, ConstU64, Contains}, + traits::{ConstU8, ConstU64, Contains}, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, IdentityFee, Weight, @@ -56,8 +54,6 @@ use support::{ parameter_types, construct_runtime, }; -use transaction_payment::CurrencyAdapter; - use babe::AuthorityId as BabeId; use grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; @@ -153,22 +149,10 @@ impl Contains for CallFilter { return matches!(call, timestamp::Call::set { .. }); } - if let RuntimeCall::Balances(call) = call { - return matches!(call, balances::Call::transfer { .. } | balances::Call::transfer_all { .. }); + if let RuntimeCall::Coins(call) = call { + return matches!(call, coins::Call::transfer { .. } | coins::Call::burn { .. }); } - if let RuntimeCall::Assets(call) = call { - return matches!( - call, - assets::Call::approve_transfer { .. } | - assets::Call::cancel_approval { .. } | - assets::Call::transfer { .. } | - assets::Call::transfer_approved { .. } - ); - } - if let RuntimeCall::Tokens(call) = call { - return matches!(call, tokens::Call::burn { .. }); - } if let RuntimeCall::InInstructions(call) = call { return matches!(call, in_instructions::Call::execute_batch { .. }); } @@ -217,7 +201,7 @@ impl system::Config for Runtime { type OnKilledAccount = (); type OnSetCode = (); - type AccountData = balances::AccountData; + type AccountData = (); type SystemWeightInfo = (); type SS58Prefix = SS58Prefix; // TODO: Remove for Bech32m @@ -231,83 +215,16 @@ impl timestamp::Config for Runtime { type WeightInfo = (); } -impl balances::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - - type Balance = SubstrateAmount; - - type ReserveIdentifier = (); - type FreezeIdentifier = (); - type RuntimeHoldReason = (); - - type MaxLocks = (); - type MaxReserves = (); - type MaxHolds = (); - type MaxFreezes = (); - - type DustRemoval = (); - type ExistentialDeposit = ConstU64<1>; - // TODO: What's the benefit to this? - type AccountStore = System; - type WeightInfo = balances::weights::SubstrateWeight; -} - impl transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter; + type OnChargeTransaction = Coins; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } -#[cfg(feature = "runtime-benchmarks")] -pub struct SeraiAssetBenchmarkHelper; -#[cfg(feature = "runtime-benchmarks")] -impl assets::BenchmarkHelper for SeraiAssetBenchmarkHelper { - fn create_asset_id_parameter(id: u32) -> Coin { - match (id % 4) + 1 { - 1 => Coin::Bitcoin, - 2 => Coin::Ether, - 3 => Coin::Dai, - 4 => Coin::Monero, - _ => panic!("(id % 4) + 1 wasn't in [1, 4]"), - } - } -} - -impl assets::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = SubstrateAmount; - type Currency = Balances; - - type AssetId = Coin; - type AssetIdParameter = Coin; - type StringLimit = ConstU32<32>; - - // Don't allow anyone to create assets - type CreateOrigin = support::traits::AsEnsureOriginWithArg>; - type ForceOrigin = system::EnsureRoot; - - // Don't charge fees nor kill accounts - type RemoveItemsLimit = ConstU32<0>; - type AssetDeposit = ConstU64<0>; - type AssetAccountDeposit = ConstU64<0>; - type MetadataDepositBase = ConstU64<0>; - type MetadataDepositPerByte = ConstU64<0>; - type ApprovalDeposit = ConstU64<0>; - - // Unused hooks - type CallbackHandle = (); - type Freezer = (); - type Extra = (); - - type WeightInfo = assets::weights::SubstrateWeight; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = SeraiAssetBenchmarkHelper; -} - -impl tokens::Config for Runtime { +impl coins::Config for Runtime { type RuntimeEvent = RuntimeEvent; } @@ -315,9 +232,7 @@ impl in_instructions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } -impl staking::Config for Runtime { - type Currency = Balances; -} +impl staking::Config for Runtime {} impl validator_sets::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -403,11 +318,9 @@ construct_runtime!( Timestamp: timestamp, - Balances: balances, TransactionPayment: transaction_payment, - Assets: assets, - Tokens: tokens, + Coins: coins, InInstructions: in_instructions, ValidatorSets: validator_sets, diff --git a/substrate/staking/pallet/Cargo.toml b/substrate/staking/pallet/Cargo.toml index 49e8b44f..16b24cf8 100644 --- a/substrate/staking/pallet/Cargo.toml +++ b/substrate/staking/pallet/Cargo.toml @@ -15,17 +15,19 @@ rustdoc-args = ["--cfg", "docsrs"] parity-scale-codec = { version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive"] } -sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } -validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } -pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false } - serai-primitives = { path = "../../primitives", default-features = false } -serai-validator-sets-primitives = { path = "../../validator-sets/primitives", default-features = false } + +coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } + +validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } + +pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false } [features] std = [ @@ -33,8 +35,14 @@ std = [ "frame-support/std", "sp-std/std", + "sp-runtime/std", + + "serai-primitives/std", + + "coins-pallet/std", "validator-sets-pallet/std", + "pallet-session/std", ] diff --git a/substrate/staking/pallet/src/lib.rs b/substrate/staking/pallet/src/lib.rs index 7c4fa730..4b01820f 100644 --- a/substrate/staking/pallet/src/lib.rs +++ b/substrate/staking/pallet/src/lib.rs @@ -6,12 +6,11 @@ pub mod pallet { use sp_std::vec::Vec; use frame_system::pallet_prelude::*; - use frame_support::{ - pallet_prelude::*, - traits::{Currency, tokens::ExistenceRequirement}, - }; + use frame_support::pallet_prelude::*; - use serai_primitives::{NetworkId, Amount, PublicKey}; + use serai_primitives::*; + + use coins_pallet::{Config as CoinsConfig, Pallet as Coins}; use validator_sets_pallet::{ primitives::{Session, ValidatorSet}, @@ -29,9 +28,8 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config + VsConfig + SessionConfig + frame_system::Config + CoinsConfig + VsConfig + SessionConfig { - type Currency: Currency; } #[pallet::pallet] @@ -98,10 +96,8 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational))] // TODO pub fn stake(origin: OriginFor, #[pallet::compact] amount: u64) -> DispatchResult { let signer = ensure_signed(origin)?; - // Serai accounts are solely public keys. Accordingly, there's no harm to letting accounts - // die. They'll simply be re-instantiated later - // AllowDeath accordingly to not add additional requirements (and therefore annoyances) - T::Currency::transfer(&signer, &Self::account(), amount, ExistenceRequirement::AllowDeath)?; + let balance = Balance { coin: Coin::Serai, amount: Amount(amount) }; + Coins::::transfer_internal(&signer, &Self::account(), balance)?; Self::add_stake(&signer, amount); Ok(()) } @@ -112,8 +108,8 @@ pub mod pallet { pub fn unstake(origin: OriginFor, #[pallet::compact] amount: u64) -> DispatchResult { let signer = ensure_signed(origin)?; Self::remove_stake(&signer, amount)?; - // This should never be out of funds as there should always be stakers. Accordingly... - T::Currency::transfer(&Self::account(), &signer, amount, ExistenceRequirement::KeepAlive)?; + let balance = Balance { coin: Coin::Serai, amount: Amount(amount) }; + Coins::::transfer_internal(&Self::account(), &signer, balance)?; Ok(()) } diff --git a/substrate/tokens/pallet/Cargo.toml b/substrate/tokens/pallet/Cargo.toml deleted file mode 100644 index 6c0e6595..00000000 --- a/substrate/tokens/pallet/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "serai-tokens-pallet" -version = "0.1.0" -description = "Mint and burn Serai tokens" -license = "AGPL-3.0-only" -authors = ["Luke Parker "] -edition = "2021" -publish = false - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2", default-features = false, features = ["derive"] } - -frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } -frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } - -pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false } - -serai-primitives = { path = "../../primitives", default-features = false } -tokens-primitives = { package = "serai-tokens-primitives", path = "../primitives", default-features = false } - -[features] -std = [ - "scale/std", - "scale-info/std", - - "frame-system/std", - "frame-support/std", - - "pallet-assets/std", - - "serai-primitives/std", -] -default = ["std"] diff --git a/substrate/tokens/pallet/src/lib.rs b/substrate/tokens/pallet/src/lib.rs deleted file mode 100644 index 47744aaa..00000000 --- a/substrate/tokens/pallet/src/lib.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![cfg_attr(not(feature = "std"), no_std)] - -pub use tokens_primitives as primitives; - -#[frame_support::pallet] -pub mod pallet { - use frame_support::pallet_prelude::*; - use frame_system::{pallet_prelude::*, RawOrigin}; - - use pallet_assets::{Config as AssetsConfig, Pallet as AssetsPallet}; - - use serai_primitives::{SubstrateAmount, Coin, Balance, PublicKey, SeraiAddress, AccountLookup}; - use primitives::{ADDRESS, OutInstruction}; - - use super::*; - - #[pallet::config] - pub trait Config: - frame_system::Config - + AssetsConfig - { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event { - // Mint is technically redundant as the assets pallet has the exact same event already - // Providing our own definition here just helps consolidate code - Mint { address: SeraiAddress, balance: Balance }, - Burn { address: SeraiAddress, balance: Balance, instruction: OutInstruction }, - } - - #[pallet::pallet] - pub struct Pallet(PhantomData); - - impl Pallet { - fn burn_internal( - address: SeraiAddress, - balance: Balance, - instruction: OutInstruction, - ) -> DispatchResult { - AssetsPallet::::burn( - RawOrigin::Signed(ADDRESS.into()).into(), - balance.coin, - address, - balance.amount.0, - )?; - Pallet::::deposit_event(Event::Burn { address, balance, instruction }); - Ok(()) - } - - pub fn mint(address: SeraiAddress, balance: Balance) { - // TODO: Prevent minting when it'd cause an amount exceeding the allocated stake - AssetsPallet::::mint( - RawOrigin::Signed(ADDRESS.into()).into(), - balance.coin, - address, - balance.amount.0, - ) - .unwrap(); - Pallet::::deposit_event(Event::Mint { address, balance }); - } - } - - #[pallet::call] - impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight((0, DispatchClass::Normal))] // TODO - pub fn burn( - origin: OriginFor, - balance: Balance, - instruction: OutInstruction, - ) -> DispatchResult { - Self::burn_internal(ensure_signed(origin)?.into(), balance, instruction) - } - } -} - -pub use pallet::*; diff --git a/tests/coordinator/src/tests/sign.rs b/tests/coordinator/src/tests/sign.rs index 3afac8af..3f9c26be 100644 --- a/tests/coordinator/src/tests/sign.rs +++ b/tests/coordinator/src/tests/sign.rs @@ -19,7 +19,7 @@ use serai_client::{ }, coins::{ primitives::{OutInstruction, OutInstructionWithBalance}, - TokensEvent, + CoinsEvent, }, in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch}, SeraiCoins, @@ -206,12 +206,13 @@ async fn sign_test() { let address = SeraiAddress::from(pair.public()); // Fund the new account to pay for fees + let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) }; serai .publish( &serai .sign( &PairSigner::new(insecure_pair_from_name("Ferdie")), - &SeraiCoins::transfer_sri(address, Amount(1_000_000_000)), + &SeraiCoins::transfer(address, balance), 0, Default::default(), ) @@ -249,29 +250,32 @@ async fn sign_test() { serai.block_by_number(block_included_in).await.unwrap().unwrap().hash(); let serai = serai.as_of(block_included_in_hash).coins(); - assert_eq!(serai.sri_balance(serai_addr).await.unwrap(), 1_000_000_000); + assert_eq!( + serai.coin_balance(Coin::Serai, serai_addr).await.unwrap(), + Amount(1_000_000_000) + ); // Verify the mint occurred as expected assert_eq!( serai.mint_events().await.unwrap(), - vec![TokensEvent::Mint { address: serai_addr, balance }] + vec![CoinsEvent::Mint { address: serai_addr, balance }] ); - assert_eq!(serai.token_supply(Coin::Bitcoin).await.unwrap(), amount); - assert_eq!(serai.token_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount); + assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), amount); + assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount); } // Trigger a burn - let out_instruction = - OutInstruction { address: ExternalAddress::new(b"external".to_vec()).unwrap(), data: None }; + let out_instruction = OutInstructionWithBalance { + balance, + instruction: OutInstruction { + address: ExternalAddress::new(b"external".to_vec()).unwrap(), + data: None, + }, + }; serai .publish( &serai - .sign( - &serai_pair, - &SeraiCoins::burn(balance, out_instruction.clone()), - 0, - Default::default(), - ) + .sign(&serai_pair, &SeraiCoins::burn(out_instruction.clone()), 0, Default::default()) .unwrap(), ) .await @@ -297,11 +301,7 @@ async fn sign_test() { assert_eq!(burn_events.len(), 1); assert_eq!( burn_events[0], - TokensEvent::Burn { - address: serai_addr, - balance, - instruction: out_instruction.clone() - } + CoinsEvent::Burn { address: serai_addr, instruction: out_instruction.clone() } ); break 'outer; } @@ -312,8 +312,8 @@ async fn sign_test() { let last_serai_block = serai.block_by_number(last_serai_block).await.unwrap().unwrap(); let last_serai_block_hash = last_serai_block.hash(); let serai = serai.as_of(last_serai_block_hash).coins(); - assert_eq!(serai.token_supply(Coin::Bitcoin).await.unwrap(), Amount(0)); - assert_eq!(serai.token_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0)); + assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), Amount(0)); + assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0)); let mut plan_id = [0; 32]; OsRng.fill_bytes(&mut plan_id); @@ -331,10 +331,7 @@ async fn sign_test() { }, network: NetworkId::Bitcoin, block: last_serai_block.number(), - burns: vec![OutInstructionWithBalance { - instruction: out_instruction.clone(), - balance: Balance { coin: Coin::Bitcoin, amount } - }], + burns: vec![out_instruction.clone()], batches: vec![], } ) diff --git a/tests/full-stack/src/tests/mint_and_burn.rs b/tests/full-stack/src/tests/mint_and_burn.rs index e2236c85..a3f6a6e4 100644 --- a/tests/full-stack/src/tests/mint_and_burn.rs +++ b/tests/full-stack/src/tests/mint_and_burn.rs @@ -15,7 +15,7 @@ use serai_client::{ }, validator_sets::primitives::{Session, ValidatorSet}, in_instructions::primitives::Shorthand, - coins::primitives::OutInstruction, + coins::primitives::{OutInstruction, OutInstructionWithBalance}, PairTrait, PairSigner, SeraiCoins, }; @@ -236,12 +236,13 @@ async fn mint_and_burn_test() { let address = SeraiAddress::from(pair.public()); // Fund the new account to pay for fees + let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) }; serai .publish( &serai .sign( &PairSigner::new(insecure_pair_from_name("Ferdie")), - &SeraiCoins::transfer_sri(address, Amount(1_000_000_000)), + &SeraiCoins::transfer(address, balance), 0, Default::default(), ) @@ -488,17 +489,15 @@ async fn mint_and_burn_test() { let serai = &serai; let serai_pair = &serai_pair; move |nonce, coin, amount, address| async move { - let out_instruction = OutInstruction { address, data: None }; + let out_instruction = OutInstructionWithBalance { + balance: Balance { coin, amount: Amount(amount) }, + instruction: OutInstruction { address, data: None }, + }; serai .publish( &serai - .sign( - serai_pair, - &SeraiCoins::burn(Balance { coin, amount: Amount(amount) }, out_instruction), - nonce, - Default::default(), - ) + .sign(serai_pair, &SeraiCoins::burn(out_instruction), nonce, Default::default()) .unwrap(), ) .await