Dex improvements (#422)

* remove dex traits&balance types

* remove liq tokens pallet in favor of coins-pallet instance

* fix tests & benchmarks

* remove liquidity tokens trait

* fix CI

* fix pr comments

* Slight renamings

* Add burn_with_instruction as a negative to LiquidityTokens CallFilter

* Remove use of One, Zero, Saturating taits in dex pallet

---------

Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
akildemir 2023-11-12 14:37:31 +03:00 committed by GitHub
parent a43815f101
commit d015ee96a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1063 additions and 2162 deletions

View file

@ -63,8 +63,6 @@ jobs:
-p serai-primitives \
-p serai-coins-primitives \
-p serai-coins-pallet \
-p serai-liquidity-tokens-pallet \
-p serai-dex-primitives \
-p serai-dex-pallet \
-p serai-validator-sets-primitives \
-p serai-validator-sets-pallet \

32
Cargo.lock generated
View file

@ -7535,7 +7535,6 @@ dependencies = [
"parity-scale-codec",
"scale-info",
"serai-coins-primitives",
"serai-dex-primitives",
"serai-primitives",
"sp-core",
"sp-runtime",
@ -7626,8 +7625,6 @@ dependencies = [
"parity-scale-codec",
"scale-info",
"serai-coins-pallet",
"serai-dex-primitives",
"serai-liquidity-tokens-pallet",
"serai-primitives",
"sp-api",
"sp-arithmetic",
@ -7637,19 +7634,6 @@ dependencies = [
"sp-std",
]
[[package]]
name = "serai-dex-primitives"
version = "0.1.0"
dependencies = [
"frame-benchmarking",
"frame-support",
"parity-scale-codec",
"scale-info",
"serai-primitives",
"sp-runtime",
"sp-std",
]
[[package]]
name = "serai-docker-tests"
version = "0.1.0"
@ -7719,20 +7703,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "serai-liquidity-tokens-pallet"
version = "0.1.0"
dependencies = [
"frame-support",
"frame-system",
"parity-scale-codec",
"scale-info",
"serai-dex-primitives",
"serai-primitives",
"sp-core",
"sp-std",
]
[[package]]
name = "serai-message-queue"
version = "0.1.0"
@ -7947,9 +7917,7 @@ dependencies = [
"scale-info",
"serai-coins-pallet",
"serai-dex-pallet",
"serai-dex-primitives",
"serai-in-instructions-pallet",
"serai-liquidity-tokens-pallet",
"serai-primitives",
"serai-signals-pallet",
"serai-validator-sets-pallet",

View file

@ -208,8 +208,8 @@ async fn handle_batch_and_burns<D: Db, Pro: Processors>(
}
}
for burn in serai.coins().burn_events().await? {
if let CoinsEvent::Burn { from: _, instruction } = burn {
for burn in serai.coins().burn_with_instruction_events().await? {
if let CoinsEvent::BurnWithInstruction { from: _, instruction } = burn {
let network = instruction.balance.coin.network();
network_had_event(&mut burns, &mut batches, network);

View file

@ -54,8 +54,6 @@ exceptions = [
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-liquidity-tokens-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-dex-primitives" },
{ allow = ["AGPL-3.0"], name = "serai-dex-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },

View file

@ -24,8 +24,8 @@ impl<'a> SeraiCoins<'a> {
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Mint { .. })).await
}
pub async fn burn_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Burn { .. })).await
pub async fn burn_with_instruction_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::BurnWithInstruction { .. })).await
}
pub async fn coin_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
@ -64,7 +64,15 @@ impl<'a> SeraiCoins<'a> {
)
}
pub fn burn(instruction: OutInstructionWithBalance) -> Payload<Composite<()>> {
Payload::new(PALLET, "burn", scale_composite(coins::Call::<Runtime>::burn { instruction }))
pub fn burn(balance: Balance) -> Payload<Composite<()>> {
Payload::new(PALLET, "burn", scale_composite(coins::Call::<Runtime>::burn { balance }))
}
pub fn burn_with_instruction(instruction: OutInstructionWithBalance) -> Payload<Composite<()>> {
Payload::new(
PALLET,
"burn_with_instruction",
scale_composite(coins::Call::<Runtime>::burn_with_instruction { instruction }),
)
}
}

View file

@ -31,12 +31,11 @@ impl<'a> SeraiDex<'a> {
PALLET,
"add_liquidity",
scale_composite(dex::Call::<Runtime>::add_liquidity {
coin1: coin,
coin2: Coin::Serai,
amount1_desired: coin_amount.0,
amount2_desired: sri_amount.0,
amount1_min: min_coin_amount.0,
amount2_min: min_sri_amount.0,
coin,
coin_desired: coin_amount.0,
sri_desired: sri_amount.0,
coin_min: min_coin_amount.0,
sri_min: min_sri_amount.0,
mint_to: address.into(),
}),
)

View file

@ -93,7 +93,7 @@ serai_test!(
&serai
.sign(
&PairSigner::new(pair),
&SeraiCoins::burn(instruction.clone()),
&SeraiCoins::burn_with_instruction(instruction.clone()),
0,
BaseExtrinsicParamsBuilder::new(),
)
@ -102,8 +102,8 @@ serai_test!(
.await;
let serai = serai.as_of(block).coins();
let events = serai.burn_events().await.unwrap();
assert_eq!(events, vec![CoinsEvent::Burn { from: address.into(), instruction }]);
let events = serai.burn_with_instruction_events().await.unwrap();
assert_eq!(events, vec![CoinsEvent::BurnWithInstruction { from: address.into(), instruction }]);
assert_eq!(serai.coin_supply(coin).await.unwrap(), Amount(0));
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), Amount(0));
})

View file

@ -36,24 +36,24 @@ serai_test!(
events,
vec![
DexEvent::PoolCreated {
pool_id: (Coin::Serai, Coin::Bitcoin),
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Bitcoin).encode())),
lp_token: 0,
pool_id: Coin::Bitcoin,
pool_account: PublicKey::from_raw(blake2_256(&Coin::Bitcoin.encode())),
lp_token: Coin::Bitcoin,
},
DexEvent::PoolCreated {
pool_id: (Coin::Serai, Coin::Ether),
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Ether).encode())),
lp_token: 1,
pool_id: Coin::Ether,
pool_account: PublicKey::from_raw(blake2_256(&Coin::Ether.encode())),
lp_token: Coin::Ether,
},
DexEvent::PoolCreated {
pool_id: (Coin::Serai, Coin::Dai),
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Dai).encode())),
lp_token: 2,
pool_id: Coin::Dai,
pool_account: PublicKey::from_raw(blake2_256(&Coin::Dai.encode())),
lp_token: Coin::Dai,
},
DexEvent::PoolCreated {
pool_id: (Coin::Serai, Coin::Monero),
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Monero).encode())),
lp_token: 3,
pool_id: Coin::Monero,
pool_account: PublicKey::from_raw(blake2_256(&Coin::Monero.encode())),
lp_token: Coin::Monero,
},
]
);
@ -93,10 +93,10 @@ serai_test!(
vec![DexEvent::LiquidityAdded {
who: pair.public(),
mint_to: pair.public(),
pool_id: (Coin::Serai, Coin::Monero),
amount1_provided: coin_amount.0,
amount2_provided: sri_amount.0,
lp_token: 3,
pool_id: Coin::Monero,
coin_amount: coin_amount.0,
sri_amount: sri_amount.0,
lp_token: Coin::Monero,
lp_token_minted: 49_999999990000
}]
);
@ -277,10 +277,10 @@ serai_test!(
vec![DexEvent::LiquidityAdded {
who: IN_INSTRUCTION_EXECUTOR.into(),
mint_to: pair.public(),
pool_id: (Coin::Serai, Coin::Bitcoin),
amount1_provided: 6_947_918_403_646,
amount2_provided: 10_000_000_000_000, // half of sent amount
lp_token: 0,
pool_id: Coin::Bitcoin,
coin_amount: 10_000_000_000_000, // half of sent amount
sri_amount: 6_947_918_403_646,
lp_token: Coin::Bitcoin,
lp_token_minted: 8333333333332
}]
);

View file

@ -24,8 +24,6 @@ sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
dex-primitives = { package = "serai-dex-primitives", path = "../../dex/primitives", default-features = false }
serai-primitives = { path = "../../primitives", default-features = false }
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
@ -40,8 +38,6 @@ std = [
"pallet-transaction-payment/std",
"dex-primitives/std",
"serai-primitives/std",
"coins-primitives/std",
]

View file

@ -2,7 +2,7 @@
#[frame_support::pallet]
pub mod pallet {
use sp_std::vec::Vec;
use sp_std::{vec::Vec, any::TypeId};
use sp_core::sr25519::Public;
use sp_runtime::{
traits::{DispatchInfoOf, PostDispatchInfoOf},
@ -14,80 +14,82 @@ pub mod pallet {
use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction};
use dex_primitives::{Currency, Coins as CoinsTrait};
use serai_primitives::*;
pub use coins_primitives as primitives;
use primitives::*;
type LiquidityTokensInstance = crate::Instance1;
#[pallet::config]
pub trait Config: frame_system::Config<AccountId = Public> {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> {
type RuntimeEvent: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::genesis_config]
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct GenesisConfig<T: Config> {
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub accounts: Vec<(T::AccountId, Balance)>,
pub _ignore: PhantomData<I>,
}
impl<T: Config> Default for GenesisConfig<T> {
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
GenesisConfig { accounts: Default::default() }
GenesisConfig { accounts: Default::default(), _ignore: Default::default() }
}
}
#[pallet::error]
pub enum Error<T> {
pub enum Error<T, I = ()> {
AmountOverflowed,
NotEnoughCoins,
SriBurnNotAllowed,
BurnWithInstructionNotAllowed,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
pub enum Event<T: Config<I>, I: 'static = ()> {
Mint { to: Public, balance: Balance },
Burn { from: Public, instruction: OutInstructionWithBalance },
SriBurn { from: Public, amount: Amount },
Burn { from: Public, balance: Balance },
BurnWithInstruction { from: Public, instruction: OutInstructionWithBalance },
Transfer { from: Public, to: Public, balance: Balance },
}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
pub struct Pallet<T, I = ()>(_);
/// 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> =
pub type Balances<T: Config<I>, I: 'static = ()> =
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>;
pub type Supply<T: Config<I>, I: 'static = ()> =
StorageMap<_, Identity, Coin, SubstrateAmount, ValueQuery>;
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
// initialize the supply of the coins
// TODO: Don't use COINS yet GenesisConfig so we can safely expand COINS
for c in &COINS {
Supply::<T>::set(c, 0);
Supply::<T, I>::set(c, 0);
}
// initialize the genesis accounts
for (account, balance) in &self.accounts {
Pallet::<T>::mint(*account, *balance).unwrap();
Pallet::<T, I>::mint(*account, *balance).unwrap();
}
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
// burn the fees collected previous block
let coin = Coin::Serai;
@ -95,66 +97,66 @@ pub mod pallet {
// 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();
Self::burn_internal(FEE_ACCOUNT.into(), Balance { coin, amount }).unwrap();
Weight::zero() // TODO
}
}
impl<T: Config> Pallet<T> {
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// 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>> {
fn decrease_balance_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
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)?;
.ok_or(Error::<T, I>::NotEnoughCoins)?;
// save
if new_amount == 0 {
Balances::<T>::remove(from, coin);
Balances::<T, I>::remove(from, coin);
} else {
Balances::<T>::set(from, coin, new_amount);
Balances::<T, I>::set(from, coin, new_amount);
}
Ok(())
}
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T>> {
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
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)?;
.ok_or(Error::<T, I>::AmountOverflowed)?;
// save
Balances::<T>::set(to, coin, new_amount);
Balances::<T, I>::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>> {
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
// 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);
.ok_or(Error::<T, I>::AmountOverflowed)?;
Supply::<T, I>::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>> {
/// Burn `balance` from the specified account.
fn burn_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
// don't waste time if amount == 0
if balance.amount.0 == 0 {
return Ok(());
@ -165,31 +167,17 @@ pub mod pallet {
// update the supply
let new_supply = Self::supply(balance.coin).checked_sub(balance.amount.0).unwrap();
Supply::<T>::set(balance.coin, new_supply);
Supply::<T, I>::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>> {
pub fn transfer_internal(
from: Public,
to: Public,
balance: Balance,
) -> Result<(), Error<T, I>> {
// update balances of accounts
Self::decrease_balance_internal(from, balance)?;
Self::increase_balance_internal(to, balance)?;
@ -199,7 +187,7 @@ pub mod pallet {
}
#[pallet::call]
impl<T: Config> Pallet<T> {
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn transfer(origin: OriginFor<T>, to: Public, balance: Balance) -> DispatchResult {
@ -208,77 +196,35 @@ pub mod pallet {
Ok(())
}
/// Burn `balance` from the caller.
#[pallet::call_index(1)]
#[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn burn(origin: OriginFor<T>, instruction: OutInstructionWithBalance) -> DispatchResult {
pub fn burn(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
let from = ensure_signed(origin)?;
Self::burn_non_sri(from, instruction)?;
Self::burn_internal(from, balance)?;
Self::deposit_event(Event::Burn { from, balance });
Ok(())
}
/// Burn `balance` with `OutInstructionWithBalance` from the caller.
/// Errors if called for SRI or Instance1 instance of this pallet.
#[pallet::call_index(2)]
#[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn burn_with_instruction(
origin: OriginFor<T>,
instruction: OutInstructionWithBalance,
) -> DispatchResult {
if instruction.balance.coin == Coin::Serai {
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
}
if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
}
impl<T: Config> Currency<T::AccountId> for Pallet<T> {
type Balance = SubstrateAmount;
fn balance(of: &Public) -> Self::Balance {
Self::balance(*of, Coin::Serai).0
}
/// TODO: make sure of coin precision here.
fn minimum_balance() -> Self::Balance {
1
}
fn transfer(
from: &Public,
to: &Public,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
Self::transfer_internal(*from, *to, balance)?;
Ok(amount)
}
fn mint(to: &Public, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::mint(*to, Balance { coin: Coin::native(), amount: Amount(amount) })?;
Ok(amount)
}
}
// TODO: Have DEX implement for Coins, not Coins implement for Coins
impl<T: Config> CoinsTrait<T::AccountId> for Pallet<T> {
type Balance = SubstrateAmount;
type CoinId = Coin;
// TODO: Swap the order of these arguments
fn balance(coin: Self::CoinId, of: &Public) -> Self::Balance {
Self::balance(*of, coin).0
}
fn minimum_balance(_: Self::CoinId) -> Self::Balance {
1
}
// TODO: Move coin next to amount
fn transfer(
coin: Self::CoinId,
from: &Public,
to: &Public,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let balance = Balance { coin, amount: Amount(amount) };
Self::transfer_internal(*from, *to, balance)?;
Ok(amount)
}
// TODO: Move coin next to amount
fn mint(
coin: Self::CoinId,
to: &Public,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::mint(*to, Balance { coin, amount: Amount(amount) })?;
Ok(amount)
let from = ensure_signed(origin)?;
Self::burn_internal(from, instruction.balance)?;
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
Ok(())
}
}

View file

@ -26,13 +26,9 @@ frame-system = { git = "https://github.com/serai-dex/substrate", default-feature
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
dex-primitives = { package = "serai-dex-primitives", path = "../primitives", default-features = false }
[dev-dependencies]
serai-primitives = { path = "../../primitives", default-features = false }
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
liquidity-tokens-pallet = { package = "serai-liquidity-tokens-pallet", path = "../../liquidity-tokens/pallet", default-features = false }
serai-primitives = { path = "../../primitives", default-features = false }
[features]
default = ["std"]
@ -49,14 +45,11 @@ std = [
"serai-primitives/std",
"dex-primitives/std",
"frame-system/std",
"frame-support/std",
"frame-benchmarking?/std",
"coins-pallet/std",
"liquidity-tokens-pallet/std",
]
runtime-benchmarks = [
"sp-runtime/runtime-benchmarks",
@ -64,8 +57,6 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"dex-primitives/runtime-benchmarks",
]
try-runtime = [
"sp-runtime/try-runtime",

View file

@ -24,247 +24,206 @@ use super::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_support::{assert_ok, storage::bounded_vec::BoundedVec};
use frame_system::RawOrigin as SystemOrigin;
use sp_runtime::traits::{Bounded, StaticLookup};
use sp_runtime::traits::StaticLookup;
use sp_std::{ops::Div, prelude::*};
use serai_primitives::{Amount, Balance};
use crate::Pallet as Dex;
use coins_pallet::Pallet as Coins;
const INITIAL_COIN_BALANCE: u64 = 1_000_000_000;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
fn get_lp_token_id<T: Config>() -> T::PoolCoinId
where
T::PoolCoinId: Into<u32>,
{
let next_id: u32 = Dex::<T>::get_next_pool_coin_id().into();
(next_id - 1).into()
}
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
fn create_coin<T: Config>(coin: &T::MultiCoinId) -> (T::AccountId, AccountIdLookupOf<T>)
where
T::CoinBalance: From<u64>,
T::Currency: Currency<T::AccountId>,
T::Coins: Coins<T::AccountId>,
{
fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
if let MultiCoinIdConversionResult::Converted(coin_id) =
T::MultiCoinIdConverter::try_convert(coin)
{
assert_ok!(T::Currency::mint(&caller, BalanceOf::<T>::max_value().div(1000u32.into())));
assert_ok!(T::Coins::mint(coin_id, &caller, INITIAL_COIN_BALANCE.into()));
}
let caller_lookup = T::Lookup::unlookup(caller);
assert_ok!(Coins::<T>::mint(
caller,
Balance { coin: Coin::native(), amount: Amount(SubstrateAmount::max_value().div(1000u64)) }
));
assert_ok!(Coins::<T>::mint(
caller,
Balance { coin: *coin, amount: Amount(INITIAL_COIN_BALANCE) }
));
(caller, caller_lookup)
}
fn create_coin_and_pool<T: Config>(
coin1: &T::MultiCoinId,
coin2: &T::MultiCoinId,
) -> (T::PoolCoinId, T::AccountId, AccountIdLookupOf<T>)
where
T::CoinBalance: From<u64>,
T::Currency: Currency<T::AccountId>,
T::Coins: Coins<T::AccountId>,
T::PoolCoinId: Into<u32>,
{
assert_eq!(coin1, &T::MultiCoinIdConverter::get_native());
coin: &Coin,
) -> (PoolCoinId, T::AccountId, AccountIdLookupOf<T>) {
let (caller, caller_lookup) = create_coin::<T>(coin);
assert_ok!(Dex::<T>::create_pool(*coin));
let (caller, caller_lookup) = create_coin::<T>(coin2);
assert_ok!(Dex::<T>::create_pool(coin2.clone()));
let lp_token = get_lp_token_id::<T>();
(lp_token, caller, caller_lookup)
(*coin, caller, caller_lookup)
}
benchmarks! {
where_clause {
where
T::CoinBalance: From<u64> + Into<u64>,
T::Currency: Currency<T::AccountId>,
T::Balance: From<u64> + Into<u64>,
T::Coins: Coins<T::AccountId>,
T::PoolCoinId: Into<u32>,
}
add_liquidity {
let coin1 = T::MultiCoinIdConverter::get_native();
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into();
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin1, &coin2);
let ed: u64 = T::Currency::minimum_balance().into();
let add_amount = 1000 + ed;
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
let add_amount: u64 = 1000;
}: _(
SystemOrigin::Signed(caller.clone()),
coin1.clone(),
coin2.clone(),
add_amount.into(),
1000.into(),
0.into(),
0.into(),
caller.clone()
SystemOrigin::Signed(caller),
coin2,
1000u64,
add_amount,
0u64,
0u64,
caller
)
verify {
let pool_id = (coin1.clone(), coin2.clone());
let pool_id = Dex::<T>::get_pool_id(coin1, coin2).unwrap();
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
&add_amount.into(),
&1000.into()
).unwrap().into();
add_amount,
1000u64,
).unwrap();
assert_eq!(
T::PoolCoins::balance(lp_token, &caller),
lp_minted.into()
LiquidityTokens::<T>::balance(caller, lp_token).0,
lp_minted
);
assert_eq!(
T::Currency::balance(&Dex::<T>::get_pool_account(&pool_id)),
add_amount.into()
Coins::<T>::balance(Dex::<T>::get_pool_account(pool_id), Coin::native()).0,
add_amount
);
assert_eq!(
T::Coins::balance(
T::BenchmarkHelper::coin_id(0),
&Dex::<T>::get_pool_account(&pool_id)
),
1000.into()
Coins::<T>::balance(
Dex::<T>::get_pool_account(pool_id),
Coin::Bitcoin,
).0,
1000
);
}
remove_liquidity {
let coin1 = T::MultiCoinIdConverter::get_native();
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into();
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin1, &coin2);
let ed: u64 = T::Currency::minimum_balance().into();
let add_amount = 100 * ed;
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
let add_amount: u64 = 100;
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
&add_amount.into(),
&1000.into()
).unwrap().into();
let remove_lp_amount = lp_minted.checked_div(10).unwrap();
add_amount,
1000u64
).unwrap();
let remove_lp_amount: u64 = lp_minted.checked_div(10).unwrap();
Dex::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
coin1.clone(),
coin2.clone(),
add_amount.into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
let total_supply =
<T::PoolCoins as LiquidityTokens<T::AccountId>>::total_issuance(lp_token.clone());
}: _(
SystemOrigin::Signed(caller.clone()),
coin1,
SystemOrigin::Signed(caller).into(),
coin2,
remove_lp_amount.into(),
0.into(),
0.into(),
caller.clone()
1000u64,
add_amount,
0u64,
0u64,
caller,
)?;
let total_supply = LiquidityTokens::<T>::supply(lp_token);
}: _(
SystemOrigin::Signed(caller),
coin2,
remove_lp_amount,
0u64,
0u64,
caller
)
verify {
let new_total_supply =
<T::PoolCoins as LiquidityTokens<T::AccountId>>::total_issuance(lp_token.clone());
let new_total_supply = LiquidityTokens::<T>::supply(lp_token);
assert_eq!(
new_total_supply,
total_supply - remove_lp_amount.into()
total_supply - remove_lp_amount
);
}
swap_exact_tokens_for_tokens {
let native = T::MultiCoinIdConverter::get_native();
let coin1: T::MultiCoinId = T::BenchmarkHelper::coin_id(1).into();
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(2).into();
let (_, caller, _) = create_coin_and_pool::<T>(&native, &coin1);
let native = Coin::native();
let coin1 = Coin::Bitcoin;
let coin2 = Coin::Ether;
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
let (_, _) = create_coin::<T>(&coin2);
let ed: u64 = T::Currency::minimum_balance().into();
let ed_bump = 2u64;
Dex::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
coin1.clone(),
// TODO: this call otherwise fails with `InsufficientLiquidityMinted`.
// might be again related to their expectance on ed being > 1.
(100 * (ed + ed_bump)).into(),
200.into(),
0.into(),
0.into(),
caller.clone(),
SystemOrigin::Signed(caller).into(),
coin1,
200u64,
// TODO: this call otherwise fails with `InsufficientLiquidityMinted` if we don't multiply
// with 3. Might be again related to their expectance on ed being > 1.
100 * 3,
0u64,
0u64,
caller,
)?;
let swap_amount = 100.into();
let swap_amount = 100u64;
// since we only allow the native-coin pools, then the worst case scenario would be to swap
// coin1-native-coin2
Dex::<T>::create_pool(coin2.clone())?;
Dex::<T>::create_pool(coin2)?;
Dex::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
coin2.clone(),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
SystemOrigin::Signed(caller).into(),
coin2,
1000u64,
500,
0u64,
0u64,
caller,
)?;
let path = vec![coin1.clone(), native.clone(), coin2.clone()];
let path = vec![coin1, native, coin2];
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
let native_balance = T::Currency::balance(&caller);
let coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller);
}: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone())
let native_balance = Coins::<T>::balance(caller, native).0;
let coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
}: _(SystemOrigin::Signed(caller), path, swap_amount, 1u64, caller)
verify {
let ed_bump = 2u64;
let new_coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller);
assert_eq!(new_coin1_balance, coin1_balance - 100.into());
let new_coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
assert_eq!(new_coin1_balance, coin1_balance - 100u64);
}
swap_tokens_for_exact_tokens {
let native = T::MultiCoinIdConverter::get_native();
let coin1: T::MultiCoinId = T::BenchmarkHelper::coin_id(1).into();
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(2).into();
let (_, caller, _) = create_coin_and_pool::<T>(&native, &coin1);
let native = Coin::native();
let coin1 = Coin::Bitcoin;
let coin2 = Coin::Ether;
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
let (_, _) = create_coin::<T>(&coin2);
let ed: u64 = T::Currency::minimum_balance().into();
Dex::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
coin1.clone(),
(1000 * ed).into(),
500.into(),
0.into(),
0.into(),
caller.clone(),
SystemOrigin::Signed(caller).into(),
coin1,
500u64,
1000,
0u64,
0u64,
caller,
)?;
// since we only allow the native-coin pools, then the worst case scenario would be to swap
// coin1-native-coin2
Dex::<T>::create_pool(coin2.clone())?;
Dex::<T>::create_pool(coin2)?;
Dex::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
coin2.clone(),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
SystemOrigin::Signed(caller).into(),
coin2,
1000u64,
500,
0u64,
0u64,
caller,
)?;
let path = vec![coin1.clone(), native.clone(), coin2.clone()];
let path = vec![coin1, native, coin2];
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller);
let coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
}: _(
SystemOrigin::Signed(caller.clone()),
SystemOrigin::Signed(caller),
path.clone(),
100.into(),
(1000 * ed).into(),
caller.clone()
100u64,
1000,
caller
)
verify {
let new_coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller);
assert_eq!(new_coin2_balance, coin2_balance + 100.into());
let new_coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
assert_eq!(new_coin2_balance, coin2_balance + 100u64);
}
impl_benchmark_test_suite!(Dex, crate::mock::new_test_ext(), crate::mock::Test);

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,6 @@ use sp_runtime::{
use serai_primitives::{Coin, Balance, Amount, system_address};
pub use coins_pallet as coins;
pub use liquidity_tokens_pallet as liquidity_tokens;
type Block = frame_system::mocking::MockBlock<Test>;
@ -46,7 +45,7 @@ construct_runtime!(
{
System: frame_system,
CoinsPallet: coins,
LiquidityTokens: liquidity_tokens,
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
Dex: dex,
}
);
@ -81,54 +80,18 @@ impl coins::Config for Test {
type RuntimeEvent = RuntimeEvent;
}
impl liquidity_tokens::Config for Test {
impl coins::Config<coins::Instance1> for Test {
type RuntimeEvent = RuntimeEvent;
}
pub struct CoinConverter;
impl MultiCoinIdConverter<Coin, Coin> for CoinConverter {
/// Returns the MultiCoinId representing the native currency of the chain.
fn get_native() -> Coin {
Coin::Serai
}
/// Returns true if the given MultiCoinId is the native currency.
fn is_native(coin: &Coin) -> bool {
coin.is_native()
}
/// If it's not native, returns the CoinId for the given MultiCoinId.
fn try_convert(coin: &Coin) -> MultiCoinIdConversionResult<Coin, Coin> {
if coin.is_native() {
MultiCoinIdConversionResult::Native
} else {
MultiCoinIdConversionResult::Converted(*coin)
}
}
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = CoinsPallet;
type CoinBalance = u64;
type CoinId = Coin;
type PoolCoinId = u32;
type Coins = CoinsPallet;
type PoolCoins = LiquidityTokens;
type WeightInfo = ();
type LPFee = ConstU32<3>; // means 0.3%
type MaxSwapPathLength = ConstU32<4>;
// 100 is good enough when the main currency has 12 decimals.
type MintMinLiquidity = ConstU64<100>;
type Balance = u64;
type HigherPrecisionBalance = u128;
type MultiCoinId = Coin;
type MultiCoinIdConverter = CoinConverter;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
@ -145,6 +108,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
.into_iter()
.map(|a| (a, Balance { coin: Coin::Serai, amount: Amount(1 << 60) }))
.collect(),
_ignore: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
// This file was originally:
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// It has been forked into a crate distributed under the AGPL 3.0.
// Please check the current distribution for up-to-date copyright and licensing information.
use super::*;
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
/// Stores the lp_token coin id a particular pool has been assigned.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
pub struct PoolInfo<PoolCoinId> {
/// Liquidity pool coin
pub lp_token: PoolCoinId,
}
/// Trait for providing methods to swap between the various coin classes.
pub trait Swap<AccountId, Balance, MultiCoinId> {
/// Swap exactly `amount_in` of coin `path[0]` for coin `path[1]`.
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
/// the amount desired.
///
/// Withdraws the `path[0]` coin from `sender`, deposits the `path[1]` coin to `send_to`,
///
/// If successful, returns the amount of `path[1]` acquired for the `amount_in`.
fn swap_exact_tokens_for_tokens(
sender: AccountId,
path: Vec<MultiCoinId>,
amount_in: Balance,
amount_out_min: Option<Balance>,
send_to: AccountId,
) -> Result<Balance, DispatchError>;
/// Take the `path[0]` coin and swap some amount for `amount_out` of the `path[1]`. If an
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
/// too costly.
///
/// Withdraws `path[0]` coin from `sender`, deposits `path[1]` coin to `send_to`,
///
/// If successful returns the amount of the `path[0]` taken to provide `path[1]`.
fn swap_tokens_for_exact_tokens(
sender: AccountId,
path: Vec<MultiCoinId>,
amount_out: Balance,
amount_in_max: Option<Balance>,
send_to: AccountId,
) -> Result<Balance, DispatchError>;
}

View file

@ -18,7 +18,7 @@
// It has been forked into a crate distributed under the AGPL 3.0.
// Please check the current distribution for up-to-date copyright and licensing information.
//! Autogenerated weights for pallet_coin_conversion
//! Autogenerated weights for Dex Pallet.
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-07-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
@ -36,10 +36,10 @@
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json
// --pallet=pallet_coin_conversion
// --pallet=serai_dex_pallet
// --chain=dev
// --header=./HEADER-APACHE2
// --output=./frame/coin-conversion/src/weights.rs
// --output=./substrate/dex/pallet/src/weights.rs
// --template=./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
@ -50,7 +50,7 @@
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for pallet_coin_conversion.
/// Weight functions needed for Dex Pallet.
pub trait WeightInfo {
fn create_pool() -> Weight;
fn add_liquidity() -> Weight;
@ -59,19 +59,19 @@ pub trait WeightInfo {
fn swap_tokens_for_exact_tokens() -> Weight;
}
/// Weights for pallet_coin_conversion using the Substrate node and recommended hardware.
/// Weights for Dex Pallet using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `CoinConversion::Pools` (r:1 w:1)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:1)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Account` (r:1 w:1)
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `CoinConversion::NextPoolCoinId` (r:1 w:1)
/// Proof: `CoinConversion::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::NextPoolCoinId` (r:1 w:1)
/// Proof: `DexPallet::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `PoolCoins::Coin` (r:1 w:1)
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `PoolCoins::Account` (r:1 w:1)
@ -85,8 +85,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(8_u64))
}
/// Storage: `CoinConversion::Pools` (r:1 w:0)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:0)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)
@ -106,8 +106,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(7_u64))
}
/// Storage: `CoinConversion::Pools` (r:1 w:0)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:0)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)
@ -161,16 +161,16 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `CoinConversion::Pools` (r:1 w:1)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:1)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Account` (r:1 w:1)
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `CoinConversion::NextPoolCoinId` (r:1 w:1)
/// Proof: `CoinConversion::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::NextPoolCoinId` (r:1 w:1)
/// Proof: `DexPallet::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `PoolCoins::Coin` (r:1 w:1)
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `PoolCoins::Account` (r:1 w:1)
@ -184,8 +184,8 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(8_u64))
}
/// Storage: `CoinConversion::Pools` (r:1 w:0)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:0)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)
@ -205,8 +205,8 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(7_u64))
}
/// Storage: `CoinConversion::Pools` (r:1 w:0)
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `DexPallet::Pools` (r:1 w:0)
/// Proof: `DexPallet::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Coins::Coin` (r:1 w:1)

View file

@ -1,44 +0,0 @@
[package]
name = "serai-dex-primitives"
version = "0.1.0"
description = "Dex pallet primitives"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex/primitives"
authors = ["Parity Technologies <admin@parity.io>, Akil Demir <aeg_asd@hotmail.com>"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
serai-primitives = { path = "../../primitives", default-features = false }
[features]
default = [ "std" ]
std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-benchmarking?/std",
"serai-primitives/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
]

View file

@ -1,15 +0,0 @@
AGPL-3.0-only license
Copyright (c) 2023 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View file

@ -1,211 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
NOTE
Individual files contain the following tag instead of the full license
text.
SPDX-License-Identifier: Apache-2.0
This enables machine processing of license information based on the SPDX
License Identifiers that are here available: http://spdx.org/licenses/

View file

@ -1,221 +0,0 @@
// This file was originally:
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// It has been forked into a crate distributed under the AGPL 3.0.
// Please check the current distribution for up-to-date copyright and licensing information.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::DispatchError;
use sp_std::vec::Vec;
use frame_support::traits::tokens::{Balance, AssetId as CoinId};
use serai_primitives::Coin;
/// Stores the lp_token coin id a particular pool has been assigned.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
pub struct PoolInfo<PoolCoinId> {
/// Liquidity pool coin
pub lp_token: PoolCoinId,
}
/// A trait that converts between a MultiCoinId and either the native currency or an CoinId.
pub trait MultiCoinIdConverter<MultiCoinId, CoinId> {
/// Returns the MultiCoinId representing the native currency of the chain.
fn get_native() -> MultiCoinId;
/// Returns true if the given MultiCoinId is the native currency.
fn is_native(coin: &MultiCoinId) -> bool;
/// If it's not native, returns the CoinId for the given MultiCoinId.
fn try_convert(coin: &MultiCoinId) -> MultiCoinIdConversionResult<MultiCoinId, CoinId>;
}
/// Result of `MultiCoinIdConverter::try_convert`.
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub enum MultiCoinIdConversionResult<MultiCoinId, CoinId> {
/// Input coin is successfully converted. Means that converted coin is supported.
Converted(CoinId),
/// Means that input coin is the chain's native coin, if it has one, so no conversion (see
/// `MultiCoinIdConverter::get_native`).
Native,
/// Means input coin is not supported for pool.
Unsupported(MultiCoinId),
}
/// Benchmark Helper
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<CoinId, MultiCoinId> {
/// Returns an `CoinId` from a given integer.
fn coin_id(coin_id: u32) -> CoinId;
}
#[cfg(feature = "runtime-benchmarks")]
mod runtime_benchmarks {
use super::*;
use serai_primitives::COINS;
impl BenchmarkHelper<Coin, Coin> for () {
fn coin_id(coin_id: u32) -> Coin {
// we shift id 1 unit to the left, since id 0 is the native coin.
COINS[(usize::try_from(coin_id).unwrap() % COINS.len()) + 1]
}
}
}
/// Trait for providing methods to swap between the various coin classes.
pub trait Swap<AccountId, Balance, MultiCoinId> {
/// Swap exactly `amount_in` of coin `path[0]` for coin `path[1]`.
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
/// the amount desired.
///
/// Withdraws the `path[0]` coin from `sender`, deposits the `path[1]` coin to `send_to`,
///
/// If successful, returns the amount of `path[1]` acquired for the `amount_in`.
fn swap_exact_tokens_for_tokens(
sender: AccountId,
path: Vec<MultiCoinId>,
amount_in: Balance,
amount_out_min: Option<Balance>,
send_to: AccountId,
) -> Result<Balance, DispatchError>;
/// Take the `path[0]` coin and swap some amount for `amount_out` of the `path[1]`. If an
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
/// too costly.
///
/// Withdraws `path[0]` coin from `sender`, deposits `path[1]` coin to `send_to`,
///
/// If successful returns the amount of the `path[0]` taken to provide `path[1]`.
fn swap_tokens_for_exact_tokens(
sender: AccountId,
path: Vec<MultiCoinId>,
amount_out: Balance,
amount_in_max: Option<Balance>,
send_to: AccountId,
) -> Result<Balance, DispatchError>;
}
// TODO: Sized should be there?
/// Native coin trait for Dex pallet.
pub trait Currency<AccountId>: Sized {
/// Balance of an Account.
type Balance: Balance;
/// Returns the balance of an account.
fn balance(of: &AccountId) -> Self::Balance;
/// Returns the minimum allowed balance of an account
fn minimum_balance() -> Self::Balance;
/// Transfers the given `amount` from `from` to `to`.
fn transfer(
from: &AccountId,
to: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// mints the given `amount` into `to`.
fn mint(to: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
}
/// External coin trait for Dex pallet.
pub trait Coins<AccountId>: Sized {
/// Balance of an Account.
type Balance: Balance;
/// Coin identifier.
type CoinId: CoinId;
/// Returns the balance of an account.
fn balance(coin: Self::CoinId, of: &AccountId) -> Self::Balance;
/// Returns the minimum allowed balance of an account
fn minimum_balance(coin: Self::CoinId) -> Self::Balance;
/// Transfers the given `amount` from `from` to `to`.
fn transfer(
coin: Self::CoinId,
from: &AccountId,
to: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// mints the given `amount` of `coin` into `to`.
fn mint(
coin: Self::CoinId,
to: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
}
/// Liquidity tokens trait for Dex pallet.
pub trait LiquidityTokens<AccountId>: Sized {
/// Amount type.
type Balance: Balance;
/// Coin identifier.
type CoinId: CoinId;
/// Returns the `token` balance of and account.
fn balance(token: Self::CoinId, of: &AccountId) -> Self::Balance;
/// Mints `amount` to `to`.
fn mint_into(
token: Self::CoinId,
to: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// Burns `amount` from `from`.
fn burn_from(
token: Self::CoinId,
from: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;
/// Returns total supply for `token`.
fn total_issuance(token: Self::CoinId) -> Self::Balance;
/// Returns an iterator of the collections in existence.
fn coin_ids() -> Vec<Self::CoinId>;
}
pub struct CoinConverter;
impl MultiCoinIdConverter<Coin, Coin> for CoinConverter {
/// Returns the MultiCoinId representing the native currency of the chain.
fn get_native() -> Coin {
Coin::native()
}
/// Returns true if the given MultiCoinId is the native currency.
fn is_native(coin: &Coin) -> bool {
coin.is_native()
}
/// If it's not native, returns the CoinId for the given MultiCoinId.
fn try_convert(coin: &Coin) -> MultiCoinIdConversionResult<Coin, Coin> {
if coin.is_native() {
MultiCoinIdConversionResult::Native
} else {
MultiCoinIdConversionResult::Converted(*coin)
}
}
}

View file

@ -28,7 +28,7 @@ pub mod pallet {
use sp_runtime::traits::Zero;
use sp_core::sr25519::Public;
use serai_primitives::{Coin, SubstrateAmount, Amount, Balance};
use serai_primitives::{Coin, Amount, Balance};
use frame_support::pallet_prelude::*;
use frame_system::{pallet_prelude::*, RawOrigin};
@ -46,12 +46,7 @@ pub mod pallet {
use super::*;
#[pallet::config]
pub trait Config:
frame_system::Config
+ CoinsConfig
+ DexConfig<MultiCoinId = Coin, CoinBalance = SubstrateAmount>
+ ValidatorSetsConfig
{
pub trait Config: frame_system::Config + CoinsConfig + DexConfig + ValidatorSetsConfig {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
@ -127,7 +122,6 @@ pub mod pallet {
Dex::<T>::add_liquidity(
origin.clone().into(),
coin,
Coin::Serai,
half,
sri_amount,
1,
@ -185,7 +179,7 @@ pub mod pallet {
// do the swap
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
Dex::<T>::swap_exact_tokens_for_tokens(
origin.into(),
origin.clone().into(),
BoundedVec::try_from(path).unwrap(),
instruction.balance.amount.0,
out_balance.amount.0,
@ -206,7 +200,7 @@ pub mod pallet {
},
balance: Balance { coin: out_balance.coin, amount: coin_balance },
};
Coins::<T>::burn_non_sri(IN_INSTRUCTION_EXECUTOR.into(), instruction)?;
Coins::<T>::burn_with_instruction(origin.into(), instruction)?;
}
}
}

View file

@ -1,46 +0,0 @@
[package]
name = "serai-liquidity-tokens-pallet"
version = "0.1.0"
description = "liquidity tokens pallet for Serai"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/liquidity-tokens/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-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
dex-primitives = { package = "serai-dex-primitives", path = "../../dex/primitives", default-features = false }
serai-primitives = { path = "../../primitives", default-features = false }
[features]
std = [
"frame-system/std",
"frame-support/std",
"sp-core/std",
"sp-std/std",
"dex-primitives/std",
"serai-primitives/std",
]
runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
]
default = ["std"]

View file

@ -1,15 +0,0 @@
AGPL-3.0-only license
Copyright (c) 2023 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View file

@ -1,152 +0,0 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[frame_support::pallet]
pub mod pallet {
use sp_core::sr25519::Public;
use sp_std::vec::Vec;
use frame_support::pallet_prelude::*;
use dex_primitives::LiquidityTokens;
use serai_primitives::*;
#[pallet::config]
pub trait Config: frame_system::Config<AccountId = Public> {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::error]
pub enum Error<T> {
AmountOverflowed,
NotEnoughCoins,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
LtMint { to: Public, token: u32, amount: Amount },
LtBurn { from: Public, token: u32, amount: Amount },
}
#[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,
Blake2_128Concat,
u32,
SubstrateAmount,
OptionQuery,
>;
/// 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<_, Blake2_128Concat, u32, SubstrateAmount, ValueQuery>;
// TODO: apis: supply, mint, burn, transfer
impl<T: Config> Pallet<T> {
/// Returns the balance of a given account for `token`.
pub fn balance(token: u32, of: T::AccountId) -> SubstrateAmount {
Self::balances(of, token).unwrap_or(0)
}
/// Mint `balance` to the given account.
///
/// Errors if any amount overflows.
pub fn mint_into(token: u32, to: Public, amount: SubstrateAmount) -> Result<(), Error<T>> {
let balance = Self::balances(to, token).unwrap_or(0);
// update the balance
let new_amount = balance.checked_add(amount).ok_or(Error::<T>::AmountOverflowed)?;
// save
Balances::<T>::set(to, token, Some(new_amount));
// update the supply
let new_supply =
Self::supply(token).checked_add(amount).ok_or(Error::<T>::AmountOverflowed)?;
Supply::<T>::set(token, new_supply);
Self::deposit_event(Event::LtMint { to, token, amount: Amount(amount) });
Ok(())
}
// Burn `balance` from the specified account.
pub fn burn_from(token: u32, from: Public, amount: SubstrateAmount) -> Result<(), Error<T>> {
let balance = Self::balances(from, token);
if balance.is_none() {
Err(Error::<T>::NotEnoughCoins)?;
}
// update the balance
let new_amount = balance.unwrap().checked_sub(amount).ok_or(Error::<T>::NotEnoughCoins)?;
// save
if new_amount == 0 {
Balances::<T>::remove(from, token);
} else {
Balances::<T>::set(from, token, Some(new_amount));
}
// update the supply
let new_supply = Self::supply(token).checked_sub(amount).unwrap();
if new_supply == 0 {
Supply::<T>::remove(token);
} else {
Supply::<T>::set(token, new_supply);
}
Self::deposit_event(Event::LtBurn { from, token, amount: Amount(amount) });
Ok(())
}
pub fn total_issuance(token: u32) -> SubstrateAmount {
Supply::<T>::get(token)
}
}
impl<T: Config> LiquidityTokens<T::AccountId> for Pallet<T> {
type Balance = SubstrateAmount;
type CoinId = u32;
fn mint_into(
token: Self::CoinId,
to: &Public,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::mint_into(token, *to, amount)?;
Ok(amount)
}
fn burn_from(
token: Self::CoinId,
from: &Public,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::burn_from(token, *from, amount)?;
Ok(amount)
}
fn total_issuance(token: Self::CoinId) -> Self::Balance {
Self::total_issuance(token)
}
fn coin_ids() -> Vec<Self::CoinId> {
Supply::<T>::iter_keys().collect::<Vec<Self::CoinId>>()
}
fn balance(token: Self::CoinId, of: &Public) -> Self::Balance {
Self::balance(token, *of)
}
}
}
pub use pallet::*;

View file

@ -41,9 +41,13 @@ fn testnet_genesis(
.into_iter()
.map(|a| (a, Balance { coin: Coin::Serai, amount: Amount(1 << 60) }))
.collect(),
_ignore: Default::default(),
},
dex: DexConfig { pools: vec![Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero] },
dex: DexConfig {
pools: vec![Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero],
_ignore: Default::default(),
},
validator_sets: ValidatorSetsConfig {
networks: serai_runtime::primitives::NETWORKS

View file

@ -40,14 +40,12 @@ frame-executive = { git = "https://github.com/serai-dex/substrate", default-feat
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
serai-primitives = { path = "../primitives", default-features = false }
serai-dex-primitives = { path = "../dex/primitives", default-features = false }
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false }
liquidity-tokens-pallet = { package = "serai-liquidity-tokens-pallet", path = "../liquidity-tokens/pallet", default-features = false }
dex-pallet = { package = "serai-dex-pallet", path = "../dex/pallet", default-features = false }
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
@ -97,14 +95,12 @@ std = [
"frame-executive/std",
"serai-primitives/std",
"serai-dex-primitives/std",
"pallet-timestamp/std",
"pallet-transaction-payment/std",
"coins-pallet/std",
"liquidity-tokens-pallet/std",
"dex-pallet/std",
"validator-sets-pallet/std",
@ -132,8 +128,6 @@ runtime-benchmarks = [
"pallet-timestamp/runtime-benchmarks",
"dex-pallet/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
]

View file

@ -17,7 +17,6 @@ pub use pallet_timestamp as timestamp;
pub use pallet_transaction_payment as transaction_payment;
pub use coins_pallet as coins;
pub use liquidity_tokens_pallet as liquidity_tokens;
pub use dex_pallet as dex;
pub use validator_sets_pallet as validator_sets;
@ -47,7 +46,7 @@ use sp_runtime::{
ApplyExtrinsicResult, Perbill,
};
use primitives::{PublicKey, SeraiAddress, Coin, AccountLookup, Signature, SubstrateAmount};
use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount};
use support::{
traits::{ConstU8, ConstU32, ConstU64, Contains},
@ -157,6 +156,12 @@ impl Contains<RuntimeCall> for CallFilter {
// All of these pallets are our own, and all of their written calls are intended to be called
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
RuntimeCall::LiquidityTokens(call) => match call {
coins::Call::transfer { .. } => true,
coins::Call::burn { .. } => true,
coins::Call::burn_with_instruction { .. } => false,
coins::Call::__Ignore(_, _) => false,
},
RuntimeCall::Dex(call) => !matches!(call, dex::Call::__Ignore(_, _)),
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
@ -234,25 +239,12 @@ impl coins::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
impl liquidity_tokens::Config for Runtime {
impl coins::Config<coins::Instance1> for Runtime {
type RuntimeEvent = RuntimeEvent;
}
impl dex::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Coins;
type Balance = SubstrateAmount;
type CoinBalance = SubstrateAmount;
// TODO: Review if this should be u64/u128 or u64/u256 (and rounding in general).
type HigherPrecisionBalance = u128;
type CoinId = Coin;
type MultiCoinId = Coin;
type MultiCoinIdConverter = serai_dex_primitives::CoinConverter;
type PoolCoinId = u32;
type Coins = Coins;
type PoolCoins = LiquidityTokens;
type LPFee = ConstU32<3>; // 0.3%
type MintMinLiquidity = ConstU64<10000>;
@ -260,9 +252,6 @@ impl dex::Config for Runtime {
type MaxSwapPathLength = ConstU32<3>; // coin1 -> SRI -> coin2
type WeightInfo = dex::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
impl validator_sets::Config for Runtime {
@ -364,7 +353,7 @@ construct_runtime!(
TransactionPayment: transaction_payment,
Coins: coins,
LiquidityTokens: liquidity_tokens,
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
Dex: dex,
ValidatorSets: validator_sets,

View file

@ -275,7 +275,12 @@ async fn sign_test() {
serai
.publish(
&serai
.sign(&serai_pair, &SeraiCoins::burn(out_instruction.clone()), 0, Default::default())
.sign(
&serai_pair,
&SeraiCoins::burn_with_instruction(out_instruction.clone()),
0,
Default::default(),
)
.unwrap(),
)
.await
@ -293,7 +298,7 @@ async fn sign_test() {
let burn_events = serai
.as_of(serai.block_by_number(last_serai_block).await.unwrap().unwrap().hash())
.coins()
.burn_events()
.burn_with_instruction_events()
.await
.unwrap();
@ -301,7 +306,10 @@ async fn sign_test() {
assert_eq!(burn_events.len(), 1);
assert_eq!(
burn_events[0],
CoinsEvent::Burn { from: serai_addr.into(), instruction: out_instruction.clone() }
CoinsEvent::BurnWithInstruction {
from: serai_addr.into(),
instruction: out_instruction.clone()
}
);
break 'outer;
}

View file

@ -508,7 +508,12 @@ async fn mint_and_burn_test() {
serai
.publish(
&serai
.sign(serai_pair, &SeraiCoins::burn(out_instruction), nonce, Default::default())
.sign(
serai_pair,
&SeraiCoins::burn_with_instruction(out_instruction),
nonce,
Default::default(),
)
.unwrap(),
)
.await