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
32 changed files with 535 additions and 445 deletions

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