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 <lukeparker5132@gmail.com>
This commit is contained in:
akildemir 2023-10-19 13:22:21 +03:00 committed by GitHub
parent 3255c0ace5
commit fdfce9e207
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 535 additions and 445 deletions

View file

@ -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 \

95
Cargo.lock generated
View file

@ -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"

View file

@ -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",

View file

@ -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<D: Db, Pro: Processors>(
}
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:?}");
}

View file

@ -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" },

View file

@ -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" }

View file

@ -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)]

View file

@ -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;

View file

@ -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<Runtime>;
pub type CoinsEvent = coins::Event<Runtime>;
#[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<Vec<TokensEvent>, SeraiError> {
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Mint { .. })).await
pub async fn mint_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Mint { .. })).await
}
pub async fn burn_events(&self) -> Result<Vec<TokensEvent>, SeraiError> {
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Burn { .. })).await
pub async fn burn_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Burn { .. })).await
}
pub async fn sri_balance(&self, address: SeraiAddress) -> Result<u64, SeraiError> {
let data: Option<
serai_runtime::system::AccountInfo<u32, serai_runtime::balances::AccountData<u64>>,
> = 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<Amount, SeraiError> {
pub async fn coin_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
Ok(Amount(
self
.0
.storage::<AssetDetails<SubstrateAmount, SeraiAddress, SubstrateAmount>>(
"Assets",
"Asset",
Some(vec![scale_value(coin)]),
)
.storage::<SubstrateAmount>(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::<AssetAccount<SubstrateAmount, SubstrateAmount, (), Public>>(
"Assets",
"Account",
Some(vec![scale_value(coin), scale_value(address)]),
.storage::<SubstrateAmount>(
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<Composite<()>> {
pub fn transfer(to: SeraiAddress, balance: Balance) -> Payload<Composite<()>> {
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::<Runtime>::transfer {
dest: to,
value: amount.0,
}),
scale_composite(serai_runtime::coins::Call::<Runtime>::transfer { to, balance }),
)
}
pub fn burn(balance: Balance, instruction: OutInstruction) -> Payload<Composite<()>> {
Payload::new(
PALLET,
"burn",
scale_composite(tokens::Call::<Runtime>::burn { balance, instruction }),
)
pub fn burn(instruction: OutInstructionWithBalance) -> Payload<Composite<()>> {
Payload::new(PALLET, "burn", scale_composite(coins::Call::<Runtime>::burn { instruction }))
}
}

View file

@ -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);
}
);

View file

@ -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));
}
);

View file

@ -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 <aeg_asd@hotmail.com>"]
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"]

View file

@ -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<AccountId = Public> + TpConfig {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::genesis_config]
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct GenesisConfig<T: Config> {
_config: PhantomData<T>,
pub accounts: Vec<(Public, Balance)>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { _config: PhantomData, accounts: Default::default() }
}
}
#[pallet::error]
pub enum Error<T> {
AmountOverflowed,
NotEnoughCoins,
SriBurnNotAllowed,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
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<T>(PhantomData<T>);
/// 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<T: Config> = 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<T: Config> = StorageMap<_, Identity, Coin, SubstrateAmount, ValueQuery>;
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
// initialize the supply of the coins
for c in COINS.iter() {
Supply::<T>::set(c, 0);
}
// initialize the genesis accounts
for (account, balance) in self.accounts.iter() {
Pallet::<T>::mint(*account, *balance).unwrap();
}
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> 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<T: Config> Pallet<T> {
/// 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<T>> {
let coin = &balance.coin;
// sub amount from account
let new_amount = Self::balances(from, coin)
.checked_sub(balance.amount.0)
.ok_or(Error::<T>::NotEnoughCoins)?;
// save
if new_amount == 0 {
Balances::<T>::remove(from, coin);
} else {
Balances::<T>::set(from, coin, new_amount);
}
Ok(())
}
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T>> {
let coin = &balance.coin;
// sub amount from account
let new_amount = Self::balances(to, coin)
.checked_add(balance.amount.0)
.ok_or(Error::<T>::AmountOverflowed)?;
// save
Balances::<T>::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<T>> {
// 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::<T>::AmountOverflowed)?;
Supply::<T>::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<T>> {
// 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::<T>::set(balance.coin, new_supply);
Ok(())
}
pub fn burn_sri(
from: Public,
amount: Amount,
) -> Result<(), Error<T>> {
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<T>> {
if instruction.balance.coin == Coin::Serai {
Err(Error::<T>::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<T>> {
// 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<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn transfer(origin: OriginFor<T>, 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<T>, instruction: OutInstructionWithBalance) -> DispatchResult {
let from = ensure_signed(origin)?;
Self::burn_non_sri(from, instruction)?;
Ok(())
}
}
impl<T: Config> OnChargeTransaction<T> for Pallet<T> {
type Balance = SubstrateAmount;
type LiquidityInfo = Option<SubstrateAmount>;
fn withdraw_fee(
who: &Public,
_call: &T::RuntimeCall,
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
fee: Self::Balance,
_tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
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<T::RuntimeCall>,
_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
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::*;

View file

@ -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 <lukeparker5132@gmail.com>"]
edition = "2021"

View file

@ -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()
);
}

View file

@ -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"]

View file

@ -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<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
@ -73,10 +73,11 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
fn execute(instruction: InInstructionWithBalance) -> Result<(), ()> {
match instruction.instruction {
InInstruction::Transfer(address) => Tokens::<T>::mint(address, instruction.balance),
InInstruction::Transfer(address) => {
Coins::<T>::mint(&address.into(), instruction.balance).map_err(|_| ())
}
_ => panic!("unsupported instruction"),
}
Ok(())
}
}

View file

@ -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"]

View file

@ -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")]

View file

@ -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<RuntimeGenesisConfig>;
@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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<RuntimeCall> 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<SubstrateAmount>;
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<Runtime>;
}
impl transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
type OnChargeTransaction = Coins;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<SubstrateAmount>;
type LengthToFee = IdentityFee<SubstrateAmount>;
type FeeMultiplierUpdate = ();
}
#[cfg(feature = "runtime-benchmarks")]
pub struct SeraiAssetBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl assets::BenchmarkHelper<Coin> 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<system::EnsureNever<PublicKey>>;
type ForceOrigin = system::EnsureRoot<PublicKey>;
// 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<Runtime>;
#[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,

View file

@ -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",
]

View file

@ -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<ValidatorId = PublicKey>
frame_system::Config + CoinsConfig + VsConfig + SessionConfig<ValidatorId = PublicKey>
{
type Currency: Currency<Self::AccountId, Balance = u64>;
}
#[pallet::pallet]
@ -98,10 +96,8 @@ pub mod pallet {
#[pallet::weight((0, DispatchClass::Operational))] // TODO
pub fn stake(origin: OriginFor<T>, #[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::<T>::transfer_internal(&signer, &Self::account(), balance)?;
Self::add_stake(&signer, amount);
Ok(())
}
@ -112,8 +108,8 @@ pub mod pallet {
pub fn unstake(origin: OriginFor<T>, #[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::<T>::transfer_internal(&Self::account(), &signer, balance)?;
Ok(())
}

View file

@ -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 <lukeparker5132@gmail.com>"]
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"]

View file

@ -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<AccountId = PublicKey, Lookup = AccountLookup>
+ AssetsConfig<AssetIdParameter = Coin, Balance = SubstrateAmount>
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
// 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<T>(PhantomData<T>);
impl<T: Config> Pallet<T> {
fn burn_internal(
address: SeraiAddress,
balance: Balance,
instruction: OutInstruction,
) -> DispatchResult {
AssetsPallet::<T>::burn(
RawOrigin::Signed(ADDRESS.into()).into(),
balance.coin,
address,
balance.amount.0,
)?;
Pallet::<T>::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::<T>::mint(
RawOrigin::Signed(ADDRESS.into()).into(),
balance.coin,
address,
balance.amount.0,
)
.unwrap();
Pallet::<T>::deposit_event(Event::Mint { address, balance });
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn burn(
origin: OriginFor<T>,
balance: Balance,
instruction: OutInstruction,
) -> DispatchResult {
Self::burn_internal(ensure_signed(origin)?.into(), balance, instruction)
}
}
}
pub use pallet::*;

View file

@ -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![],
}
)

View file

@ -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