mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Have the coins pallet emit events via serai_core_pallet
`serai_core_pallet` solely defines an accumulator for the events. We use the traditional `frame_system::Events` to store them for now and enable retrieval.
This commit is contained in:
@@ -19,37 +19,44 @@ workspace = true
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../primitives", default-features = false, features = ["serde", "non_canonical_scale_derivations"] }
|
||||
serai-abi = { path = "../abi", default-features = false, features = ["substrate"] }
|
||||
serai-core-pallet = { path = "../core", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
sp-io = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false, features = ["std"] }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"serai-abi/std",
|
||||
"serai-core-pallet/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"serai-abi/try-runtime",
|
||||
"serai-core-pallet/try-runtime",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"frame-system/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
|
||||
"serai-core-pallet/runtime-benchmarks",
|
||||
]
|
||||
|
||||
default = ["std"]
|
||||
|
||||
@@ -11,7 +11,8 @@ mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use serai_primitives::balance::ExternalBalance;
|
||||
use serai_abi::primitives::balance::ExternalBalance;
|
||||
use serai_core_pallet::Pallet as Core;
|
||||
|
||||
/// The decider for if a mint is allowed or not.
|
||||
pub trait AllowMint {
|
||||
@@ -27,7 +28,7 @@ impl AllowMint for AlwaysAllowMint {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet {
|
||||
use core::any::TypeId;
|
||||
@@ -38,7 +39,10 @@ mod pallet {
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use serai_primitives::{coin::*, balance::*, instructions::OutInstructionWithBalance};
|
||||
use serai_abi::{
|
||||
primitives::{coin::*, balance::*, instructions::OutInstructionWithBalance},
|
||||
coins::Event,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -53,7 +57,9 @@ mod pallet {
|
||||
|
||||
/// The configuration of this pallet.
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> {
|
||||
pub trait Config<I: 'static = ()>:
|
||||
frame_system::Config<AccountId = Public> + serai_core_pallet::Config
|
||||
{
|
||||
/// What decides if mints are allowed.
|
||||
type AllowMint: AllowMint;
|
||||
}
|
||||
@@ -90,42 +96,6 @@ mod pallet {
|
||||
BurnWithInstructionNotAllowed,
|
||||
}
|
||||
|
||||
/// An event emitted.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Coins were minted.
|
||||
Mint {
|
||||
/// The account minted to.
|
||||
to: Public,
|
||||
/// The balance minted.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were transferred.
|
||||
Transfer {
|
||||
/// The account transferred from.
|
||||
from: Public,
|
||||
/// The account transferred to.
|
||||
to: Public,
|
||||
/// The balance transferred.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were burnt.
|
||||
Burn {
|
||||
/// The account burnt from.
|
||||
from: Public,
|
||||
/// The balance burnt.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were burnt with an instruction.
|
||||
BurnWithInstruction {
|
||||
/// The account burnt from.
|
||||
from: Public,
|
||||
/// The instruction, and associated balance.
|
||||
instruction: OutInstructionWithBalance,
|
||||
},
|
||||
}
|
||||
|
||||
/// The Pallet struct.
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
@@ -151,6 +121,16 @@ mod pallet {
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn emit_event(event: Event) {
|
||||
if TypeId::of::<I>() == TypeId::of::<CoinsInstance>() {
|
||||
Core::<T>::emit_event(event)
|
||||
} else if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
|
||||
// The DEX pallet is expected to emit this event
|
||||
} else {
|
||||
panic!("unrecognized instance type for `coins::Pallet` made it into an execution context")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the balance of `coin` for the specified account.
|
||||
pub fn balance(
|
||||
of: impl scale::EncodeLike<Public>,
|
||||
@@ -195,10 +175,10 @@ mod pallet {
|
||||
/// Mint `balance` to the given account.
|
||||
///
|
||||
/// Errors if any amount overflows.
|
||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
pub fn mint(to: Public, coins: Balance) -> Result<(), Error<T, I>> {
|
||||
{
|
||||
// If this is an external coin, check if we can mint it
|
||||
let external_balance = ExternalBalance::try_from(balance);
|
||||
let external_balance = ExternalBalance::try_from(coins);
|
||||
let can_mint_external = external_balance.as_ref().map(T::AllowMint::is_allowed);
|
||||
// If it was native to the Serai network, we can always mint it
|
||||
let can_mint = can_mint_external.unwrap_or(true);
|
||||
@@ -208,14 +188,14 @@ mod pallet {
|
||||
}
|
||||
|
||||
// update the balance
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
Self::increase_balance_internal(to, coins)?;
|
||||
|
||||
// update the supply
|
||||
let new_supply = (Supply::<T, I>::get(balance.coin) + balance.amount)
|
||||
.ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
Supply::<T, I>::set(balance.coin, new_supply);
|
||||
let new_supply =
|
||||
(Supply::<T, I>::get(coins.coin) + coins.amount).ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
Supply::<T, I>::set(coins.coin, new_supply);
|
||||
|
||||
Self::deposit_event(Event::Mint { to, balance });
|
||||
Self::emit_event(Event::Mint { to: to.into(), coins });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -238,12 +218,12 @@ mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfer `balance` from `from` to `to`.
|
||||
pub fn transfer_fn(from: Public, to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
/// Transfer `coins` from `from` to `to`.
|
||||
pub fn transfer_fn(from: Public, to: Public, coins: Balance) -> Result<(), Error<T, I>> {
|
||||
// update balances of accounts
|
||||
Self::decrease_balance_internal(from, balance)?;
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
Self::deposit_event(Event::Transfer { from, to, balance });
|
||||
Self::decrease_balance_internal(from, coins)?;
|
||||
Self::increase_balance_internal(to, coins)?;
|
||||
Self::emit_event(Event::Transfer { from: from.into(), to: to.into(), coins });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -253,19 +233,19 @@ mod pallet {
|
||||
/// Transfer `balance` from the signer to `to`.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn transfer(origin: OriginFor<T>, to: Public, balance: Balance) -> DispatchResult {
|
||||
pub fn transfer(origin: OriginFor<T>, to: Public, coins: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::transfer_fn(from, to, balance)?;
|
||||
Self::transfer_fn(from, to, coins)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the signer.
|
||||
/// Burn `coins` from the signer.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
|
||||
pub fn burn(origin: OriginFor<T>, coins: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, balance)?;
|
||||
Self::deposit_event(Event::Burn { from, balance });
|
||||
Self::burn_internal(from, coins)?;
|
||||
Self::emit_event(Event::Burn { from: from.into(), coins });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -283,7 +263,7 @@ mod pallet {
|
||||
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, instruction.balance.into())?;
|
||||
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
||||
Self::emit_event(Event::BurnWithInstruction { from: from.into(), instruction });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Test environment for Coins pallet.
|
||||
|
||||
use sp_runtime::BuildStorage;
|
||||
use borsh::BorshDeserialize;
|
||||
|
||||
use frame_support::{derive_impl, construct_runtime};
|
||||
use frame_support::{sp_runtime::BuildStorage, derive_impl, construct_runtime};
|
||||
|
||||
use crate::{self as coins, CoinsInstance};
|
||||
|
||||
@@ -10,6 +10,7 @@ construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Core: serai_core_pallet,
|
||||
Coins: coins::<CoinsInstance>,
|
||||
}
|
||||
);
|
||||
@@ -17,15 +18,28 @@ construct_runtime!(
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Lookup = frame_support::sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
}
|
||||
|
||||
impl serai_core_pallet::Config for Test {}
|
||||
|
||||
impl crate::Config<CoinsInstance> for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AllowMint = crate::AlwaysAllowMint;
|
||||
}
|
||||
|
||||
impl TryFrom<RuntimeEvent> for serai_abi::Event {
|
||||
type Error = ();
|
||||
fn try_from(event: RuntimeEvent) -> Result<serai_abi::Event, ()> {
|
||||
match event {
|
||||
RuntimeEvent::Core(serai_core_pallet::Event::Event(event)) => {
|
||||
Ok(serai_abi::Event::deserialize_reader(&mut event.as_slice()).unwrap())
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use sp_core::{Pair as _, sr25519::Pair};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
use serai_primitives::{coin::*, balance::*, address::*, instructions::*};
|
||||
use serai_abi::primitives::{coin::*, balance::*, address::*, instructions::*};
|
||||
|
||||
use crate::mock::*;
|
||||
|
||||
pub type CoinsEvent = crate::Event<Test, crate::CoinsInstance>;
|
||||
pub type CoinsEvent = serai_abi::coins::Event;
|
||||
|
||||
#[test]
|
||||
fn mint() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Core::start_transaction();
|
||||
|
||||
// minting u64::MAX should work
|
||||
let coin = Coin::Serai;
|
||||
let to = Pair::generate().0.public();
|
||||
@@ -25,10 +27,10 @@ fn mint() {
|
||||
assert_eq!(Coins::supply(coin), balance.amount);
|
||||
|
||||
// test events
|
||||
let mint_events = System::events()
|
||||
let mint_events = Core::events()
|
||||
.iter()
|
||||
.filter_map(|event| {
|
||||
if let RuntimeEvent::Coins(e) = &event.event {
|
||||
if let serai_abi::Event::Coins(e) = &event {
|
||||
if matches!(e, CoinsEvent::Mint { .. }) {
|
||||
Some(e.clone())
|
||||
} else {
|
||||
@@ -40,13 +42,15 @@ fn mint() {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(mint_events, vec![CoinsEvent::Mint { to, balance }]);
|
||||
assert_eq!(mint_events, vec![CoinsEvent::Mint { to: to.into(), coins: balance }]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burn_with_instruction() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Core::start_transaction();
|
||||
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let to = Pair::generate().0.public();
|
||||
@@ -76,10 +80,10 @@ fn burn_with_instruction() {
|
||||
assert_eq!(Coins::balance(to, coin), Amount(0));
|
||||
assert_eq!(Coins::supply(coin), Amount(0));
|
||||
|
||||
let burn_events = System::events()
|
||||
let burn_events = Core::events()
|
||||
.iter()
|
||||
.filter_map(|event| {
|
||||
if let RuntimeEvent::Coins(e) = &event.event {
|
||||
if let serai_abi::Event::Coins(e) = &event {
|
||||
if matches!(e, CoinsEvent::BurnWithInstruction { .. }) {
|
||||
Some(e.clone())
|
||||
} else {
|
||||
@@ -91,13 +95,15 @@ fn burn_with_instruction() {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(burn_events, vec![CoinsEvent::BurnWithInstruction { from: to, instruction }]);
|
||||
assert_eq!(burn_events, vec![CoinsEvent::BurnWithInstruction { from: to.into(), instruction }]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Core::start_transaction();
|
||||
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let from = Pair::generate().0.public();
|
||||
|
||||
Reference in New Issue
Block a user