mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 04:09:23 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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 }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
);
|
||||
|
||||
49
substrate/coins/pallet/Cargo.toml
Normal file
49
substrate/coins/pallet/Cargo.toml
Normal 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"]
|
||||
278
substrate/coins/pallet/src/lib.rs
Normal file
278
substrate/coins/pallet/src/lib.rs
Normal 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::*;
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]
|
||||
@@ -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::*;
|
||||
Reference in New Issue
Block a user