mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
5% buffer for swap mints; 20% restrction on out of pool value
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -7397,6 +7397,7 @@ dependencies = [
|
|||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-primitives",
|
"serai-coins-primitives",
|
||||||
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use serai_client::{
|
|||||||
SeraiAddress, PublicKey,
|
SeraiAddress, PublicKey,
|
||||||
},
|
},
|
||||||
in_instructions::primitives::{
|
in_instructions::primitives::{
|
||||||
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress,
|
InInstruction, InInstructionWithBalance, Batch, ADD_LIQUIDITY_ACCOUNT, SWAP_ACCOUNT, OutAddress,
|
||||||
},
|
},
|
||||||
dex::DexEvent,
|
dex::DexEvent,
|
||||||
Serai,
|
Serai,
|
||||||
@@ -270,7 +270,7 @@ serai_test!(
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::LiquidityAdded {
|
vec![DexEvent::LiquidityAdded {
|
||||||
who: IN_INSTRUCTION_EXECUTOR,
|
who: ADD_LIQUIDITY_ACCOUNT,
|
||||||
mint_to: pair.public().into(),
|
mint_to: pair.public().into(),
|
||||||
pool_id: Coin::Bitcoin,
|
pool_id: Coin::Bitcoin,
|
||||||
coin_amount: 10_000_000_000_000, // half of sent amount
|
coin_amount: 10_000_000_000_000, // half of sent amount
|
||||||
@@ -357,8 +357,8 @@ serai_test!(
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
who: IN_INSTRUCTION_EXECUTOR,
|
who: SWAP_ACCOUNT,
|
||||||
send_to: IN_INSTRUCTION_EXECUTOR,
|
send_to: SWAP_ACCOUNT,
|
||||||
path,
|
path,
|
||||||
amount_in: 20_000_000_000_000,
|
amount_in: 20_000_000_000_000,
|
||||||
amount_out: 11066655622377
|
amount_out: 11066655622377
|
||||||
@@ -396,7 +396,7 @@ serai_test!(
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
who: IN_INSTRUCTION_EXECUTOR,
|
who: SWAP_ACCOUNT,
|
||||||
send_to: out_address.as_native().unwrap(),
|
send_to: out_address.as_native().unwrap(),
|
||||||
path,
|
path,
|
||||||
amount_in: 20_000_000_000_000,
|
amount_in: 20_000_000_000_000,
|
||||||
@@ -434,7 +434,7 @@ serai_test!(
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
who: IN_INSTRUCTION_EXECUTOR,
|
who: SWAP_ACCOUNT,
|
||||||
send_to: out_address.as_native().unwrap(),
|
send_to: out_address.as_native().unwrap(),
|
||||||
path,
|
path,
|
||||||
amount_in: 10_000_000_000_000,
|
amount_in: 10_000_000_000_000,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d
|
|||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
||||||
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||||
|
in-ins-primitives = { package = "serai-in-instructions-primitives", path = "../../in-instructions/primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
@@ -47,6 +48,7 @@ std = [
|
|||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"coins-primitives/std",
|
"coins-primitives/std",
|
||||||
|
"in-ins-primitives/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
use serai_primitives::{Coin, SubstrateAmount, Balance};
|
use serai_primitives::{Coin, SubstrateAmount, Balance};
|
||||||
|
|
||||||
pub trait AllowMint {
|
pub trait AllowMint {
|
||||||
fn is_allowed(balance: &Balance) -> bool;
|
fn is_allowed(balance: &Balance, for_swap: bool) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AllowMint for () {
|
impl AllowMint for () {
|
||||||
fn is_allowed(_: &Balance) -> bool {
|
fn is_allowed(_: &Balance, _: bool) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,8 @@ pub mod pallet {
|
|||||||
pub use coins_primitives as primitives;
|
pub use coins_primitives as primitives;
|
||||||
use primitives::*;
|
use primitives::*;
|
||||||
|
|
||||||
|
use in_ins_primitives::SWAP_ACCOUNT;
|
||||||
|
|
||||||
type LiquidityTokensInstance = crate::Instance1;
|
type LiquidityTokensInstance = crate::Instance1;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
@@ -159,7 +161,7 @@ pub mod pallet {
|
|||||||
///
|
///
|
||||||
/// Errors if any amount overflows.
|
/// Errors if any amount overflows.
|
||||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||||
if !T::AllowMint::is_allowed(&balance) {
|
if !T::AllowMint::is_allowed(&balance, to == SWAP_ACCOUNT.into()) {
|
||||||
Err(Error::<T, I>::MintNotAllowed)?;
|
Err(Error::<T, I>::MintNotAllowed)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ pub mod pallet {
|
|||||||
/// Map from `PoolId` to `()`. This establishes whether a pool has been officially
|
/// Map from `PoolId` to `()`. This establishes whether a pool has been officially
|
||||||
/// created rather than people sending tokens directly to a pool's public account.
|
/// created rather than people sending tokens directly to a pool's public account.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn pools)]
|
||||||
pub type Pools<T: Config> = StorageMap<_, Blake2_128Concat, PoolId, (), OptionQuery>;
|
pub type Pools<T: Config> = StorageMap<_, Blake2_128Concat, PoolId, (), OptionQuery>;
|
||||||
|
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ pub mod pallet {
|
|||||||
pub enum Error<T> {
|
pub enum Error<T> {
|
||||||
/// Coin and OutAddress types don't match.
|
/// Coin and OutAddress types don't match.
|
||||||
InvalidAddressForCoin,
|
InvalidAddressForCoin,
|
||||||
|
/// SRI minting is not allowed.
|
||||||
|
CantMintSRI,
|
||||||
|
/// There is too much coin that is not in the pool.
|
||||||
|
TooMuchCoinOutOfPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
@@ -85,6 +89,34 @@ pub mod pallet {
|
|||||||
fn execute(instruction: InInstructionWithBalance) -> Result<(), DispatchError> {
|
fn execute(instruction: InInstructionWithBalance) -> Result<(), DispatchError> {
|
||||||
match instruction.instruction {
|
match instruction.instruction {
|
||||||
InInstruction::Transfer(address) => {
|
InInstruction::Transfer(address) => {
|
||||||
|
let coin = instruction.balance.coin;
|
||||||
|
if coin == Coin::Serai {
|
||||||
|
Err(Error::<T>::CantMintSRI)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check how much in-pool vs out-of-pool if we have a pool and has certain amount of
|
||||||
|
// liquidty available.
|
||||||
|
let pool_id = Dex::<T>::get_pool_id(coin, Coin::Serai).unwrap();
|
||||||
|
if Dex::<T>::pools(pool_id).is_some() {
|
||||||
|
let pool_account = Dex::<T>::get_pool_account(pool_id);
|
||||||
|
let in_pool_sri = Coins::<T>::balance(pool_account, Coin::Serai).0;
|
||||||
|
|
||||||
|
// TODO: should be a const where?
|
||||||
|
if in_pool_sri >= 10_000 * 10_u64.pow(8) {
|
||||||
|
let in_pool = Coins::<T>::balance(pool_account, coin).0;
|
||||||
|
|
||||||
|
// get out-of-pool
|
||||||
|
let total = Coins::<T>::supply(coin);
|
||||||
|
let out_of_pool = total - in_pool;
|
||||||
|
let new_out_of_pool = out_of_pool.saturating_add(instruction.balance.amount.0);
|
||||||
|
|
||||||
|
// only allow if out-of-pool is less than 20% of pool value
|
||||||
|
if new_out_of_pool > in_pool / 5 {
|
||||||
|
Err(Error::<T>::TooMuchCoinOutOfPool)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Coins::<T>::mint(address.into(), instruction.balance)?;
|
Coins::<T>::mint(address.into(), instruction.balance)?;
|
||||||
}
|
}
|
||||||
InInstruction::Dex(call) => {
|
InInstruction::Dex(call) => {
|
||||||
@@ -93,11 +125,11 @@ pub mod pallet {
|
|||||||
// called directly from Serai with a native transaction.
|
// called directly from Serai with a native transaction.
|
||||||
match call {
|
match call {
|
||||||
DexCall::SwapAndAddLiquidity(address) => {
|
DexCall::SwapAndAddLiquidity(address) => {
|
||||||
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
let origin = RawOrigin::Signed(ADD_LIQUIDITY_ACCOUNT.into());
|
||||||
let coin = instruction.balance.coin;
|
let coin = instruction.balance.coin;
|
||||||
|
|
||||||
// mint the given coin on the account
|
// mint the given coin on the account
|
||||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
Coins::<T>::mint(ADD_LIQUIDITY_ACCOUNT.into(), instruction.balance)?;
|
||||||
|
|
||||||
// swap half of it for SRI
|
// swap half of it for SRI
|
||||||
let half = instruction.balance.amount.0 / 2;
|
let half = instruction.balance.amount.0 / 2;
|
||||||
@@ -107,11 +139,11 @@ pub mod pallet {
|
|||||||
path,
|
path,
|
||||||
half,
|
half,
|
||||||
1, // minimum out, so we accept whatever we get.
|
1, // minimum out, so we accept whatever we get.
|
||||||
IN_INSTRUCTION_EXECUTOR.into(),
|
ADD_LIQUIDITY_ACCOUNT.into(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// get how much we got for our swap
|
// get how much we got for our swap
|
||||||
let sri_amount = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai).0;
|
let sri_amount = Coins::<T>::balance(ADD_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0;
|
||||||
|
|
||||||
// add liquidity
|
// add liquidity
|
||||||
Dex::<T>::add_liquidity(
|
Dex::<T>::add_liquidity(
|
||||||
@@ -127,18 +159,18 @@ pub mod pallet {
|
|||||||
// TODO: minimums are set to 1 above to guarantee successful adding liq call.
|
// TODO: minimums are set to 1 above to guarantee successful adding liq call.
|
||||||
// Ideally we either get this info from user or send the leftovers back to user.
|
// Ideally we either get this info from user or send the leftovers back to user.
|
||||||
// Let's send the leftovers back to user for now.
|
// Let's send the leftovers back to user for now.
|
||||||
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin);
|
let coin_balance = Coins::<T>::balance(ADD_LIQUIDITY_ACCOUNT.into(), coin);
|
||||||
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
let sri_balance = Coins::<T>::balance(ADD_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
||||||
if coin_balance != Amount(0) {
|
if coin_balance != Amount(0) {
|
||||||
Coins::<T>::transfer_internal(
|
Coins::<T>::transfer_internal(
|
||||||
IN_INSTRUCTION_EXECUTOR.into(),
|
ADD_LIQUIDITY_ACCOUNT.into(),
|
||||||
address.into(),
|
address.into(),
|
||||||
Balance { coin, amount: coin_balance },
|
Balance { coin, amount: coin_balance },
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if sri_balance != Amount(0) {
|
if sri_balance != Amount(0) {
|
||||||
Coins::<T>::transfer_internal(
|
Coins::<T>::transfer_internal(
|
||||||
IN_INSTRUCTION_EXECUTOR.into(),
|
ADD_LIQUIDITY_ACCOUNT.into(),
|
||||||
address.into(),
|
address.into(),
|
||||||
Balance { coin: Coin::Serai, amount: sri_balance },
|
Balance { coin: Coin::Serai, amount: sri_balance },
|
||||||
)?;
|
)?;
|
||||||
@@ -154,7 +186,7 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mint the given coin on our account
|
// mint the given coin on our account
|
||||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
Coins::<T>::mint(SWAP_ACCOUNT.into(), instruction.balance)?;
|
||||||
|
|
||||||
// get the path
|
// get the path
|
||||||
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
||||||
@@ -166,13 +198,13 @@ pub mod pallet {
|
|||||||
// if the address is internal, we can directly swap to it. if not, we swap to
|
// if the address is internal, we can directly swap to it. if not, we swap to
|
||||||
// ourselves and burn the coins to send them back on the external chain.
|
// ourselves and burn the coins to send them back on the external chain.
|
||||||
let send_to = if send_to_external {
|
let send_to = if send_to_external {
|
||||||
IN_INSTRUCTION_EXECUTOR
|
SWAP_ACCOUNT
|
||||||
} else {
|
} else {
|
||||||
out_address.clone().as_native().unwrap()
|
out_address.clone().as_native().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
// do the swap
|
// do the swap
|
||||||
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
let origin = RawOrigin::Signed(SWAP_ACCOUNT.into());
|
||||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||||
origin.clone().into(),
|
origin.clone().into(),
|
||||||
BoundedVec::try_from(path).unwrap(),
|
BoundedVec::try_from(path).unwrap(),
|
||||||
@@ -185,8 +217,7 @@ pub mod pallet {
|
|||||||
// if it is requested to an external address.
|
// if it is requested to an external address.
|
||||||
if send_to_external {
|
if send_to_external {
|
||||||
// see how much we got
|
// see how much we got
|
||||||
let coin_balance =
|
let coin_balance = Coins::<T>::balance(SWAP_ACCOUNT.into(), out_balance.coin);
|
||||||
Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), out_balance.coin);
|
|
||||||
let instruction = OutInstructionWithBalance {
|
let instruction = OutInstructionWithBalance {
|
||||||
instruction: OutInstruction {
|
instruction: OutInstruction {
|
||||||
address: out_address.as_external().unwrap(),
|
address: out_address.as_external().unwrap(),
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ pub use shorthand::*;
|
|||||||
|
|
||||||
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
|
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
|
||||||
|
|
||||||
// This is the account which will be the origin for any dispatched `InInstruction`s.
|
// This is the account which will be the origin for add liquidity instructions.
|
||||||
pub const IN_INSTRUCTION_EXECUTOR: SeraiAddress = system_address(b"InInstructions-executor");
|
pub const ADD_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"add-liquidty-account");
|
||||||
|
|
||||||
|
// This is the account which will be the origin for swap intructions.
|
||||||
|
pub const SWAP_ACCOUNT: SeraiAddress = system_address(b"swap-account");
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||||
|
|||||||
@@ -1031,14 +1031,20 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> AllowMint for Pallet<T> {
|
impl<T: Config> AllowMint for Pallet<T> {
|
||||||
fn is_allowed(balance: &Balance) -> bool {
|
fn is_allowed(balance: &Balance, for_swap: bool) -> bool {
|
||||||
// get the required stake
|
// get the required stake
|
||||||
let current_required = Self::required_stake_for_network(balance.coin.network());
|
let current_required = Self::required_stake_for_network(balance.coin.network());
|
||||||
let new_required = current_required + Self::required_stake(balance);
|
let new_required = current_required + Self::required_stake(balance);
|
||||||
|
|
||||||
// get the total stake for the network & compare.
|
// get the total stake for the network & compare.
|
||||||
let staked = Self::total_allocated_stake(balance.coin.network()).unwrap_or(Amount(0));
|
let mut staked = Self::total_allocated_stake(balance.coin.network()).unwrap_or(Amount(0)).0;
|
||||||
staked.0 >= new_required
|
|
||||||
|
// we leave 5% buffer on our stake for swap mints.
|
||||||
|
if !for_swap {
|
||||||
|
staked -= staked / 20; // TODO: this should be a const but where?
|
||||||
|
}
|
||||||
|
|
||||||
|
staked >= new_required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user