mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Add Dex pallet (#407)
* Move pallet-asset-conversion * update licensing * initial integration * Integrate Currency & Assets types * integrate liquidity tokens * fmt * integrate dex pallet tests * fmt * compilation error fixes * integrate dex benchmarks * fmt * cargo clippy * replace all occurrences of "asset" with "coin" * add the actual add liq/swap logic to in-instructions * add client side & tests * fix deny * Lint and changes - Renames InInstruction::AddLiquidity to InInstruction::SwapAndAddLiquidity - Makes create_pool an internal function - Makes dex-pallet exclusively create pools against a native coin - Removes various fees - Adds new crates to GH workflow * Fix rebase artifacts * Correct other rebase artifact * Correct CI specification for liquidity-tokens * Correct primitives' test to the standardized pallet account scheme --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
@@ -17,10 +17,11 @@ thiserror = { version = "1", optional = true }
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
@@ -29,6 +30,7 @@ serai-primitives = { path = "../../primitives", default-features = false }
|
||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/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 }
|
||||
|
||||
[features]
|
||||
@@ -38,10 +40,11 @@ std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-std/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-core/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
@@ -50,6 +53,7 @@ std = [
|
||||
"in-instructions-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
]
|
||||
default = ["std"]
|
||||
|
||||
@@ -23,14 +23,21 @@ pub enum PalletError {
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use sp_std::vec;
|
||||
use sp_application_crypto::RuntimePublic;
|
||||
use sp_runtime::traits::Zero;
|
||||
use sp_core::sr25519::Public;
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use serai_primitives::{Coin, SubstrateAmount, Amount, Balance};
|
||||
|
||||
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||
|
||||
use coins_pallet::{
|
||||
Config as CoinsConfig, Pallet as Coins,
|
||||
primitives::{OutInstruction, OutInstructionWithBalance},
|
||||
};
|
||||
use dex_pallet::{Config as DexConfig, Pallet as Dex};
|
||||
use validator_sets_pallet::{
|
||||
primitives::{Session, ValidatorSet},
|
||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||
@@ -39,7 +46,12 @@ pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + ValidatorSetsConfig + CoinsConfig {
|
||||
pub trait Config:
|
||||
frame_system::Config
|
||||
+ CoinsConfig
|
||||
+ DexConfig<MultiCoinId = Coin, CoinBalance = SubstrateAmount>
|
||||
+ ValidatorSetsConfig
|
||||
{
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
@@ -50,6 +62,12 @@ pub mod pallet {
|
||||
InstructionFailure { network: NetworkId, id: u32, index: u32 },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Coin and OutAddress types don't match.
|
||||
InvalidAddressForCoin,
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
@@ -79,7 +97,120 @@ pub mod pallet {
|
||||
InInstruction::Transfer(address) => {
|
||||
Coins::<T>::mint(address.into(), instruction.balance)?;
|
||||
}
|
||||
_ => panic!("unsupported instruction"),
|
||||
InInstruction::Dex(call) => {
|
||||
// This will only be initiated by external chain transactions. That is why we only need
|
||||
// add liquidity and swaps. Other functionalities (such as remove_liq, etc) will be
|
||||
// called directly from Serai with a native transaction.
|
||||
match call {
|
||||
DexCall::SwapAndAddLiquidity(address) => {
|
||||
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
||||
let coin = instruction.balance.coin;
|
||||
|
||||
// mint the given coin on the account
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||
|
||||
// swap half of it for SRI
|
||||
let half = instruction.balance.amount.0 / 2;
|
||||
let path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||
origin.clone().into(),
|
||||
path,
|
||||
half,
|
||||
1, // minimum out, so we accept whatever we get.
|
||||
IN_INSTRUCTION_EXECUTOR.into(),
|
||||
)?;
|
||||
|
||||
// get how much we got for our swap
|
||||
let sri_amount = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai).0;
|
||||
|
||||
// add liquidity
|
||||
Dex::<T>::add_liquidity(
|
||||
origin.clone().into(),
|
||||
coin,
|
||||
Coin::Serai,
|
||||
half,
|
||||
sri_amount,
|
||||
1,
|
||||
1,
|
||||
address.into(),
|
||||
)?;
|
||||
|
||||
// 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.
|
||||
// Let's send the leftovers back to user for now.
|
||||
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin);
|
||||
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
||||
if coin_balance != Amount(0) {
|
||||
Coins::<T>::transfer_internal(
|
||||
IN_INSTRUCTION_EXECUTOR.into(),
|
||||
address.into(),
|
||||
Balance { coin, amount: coin_balance },
|
||||
)?;
|
||||
}
|
||||
if sri_balance != Amount(0) {
|
||||
Coins::<T>::transfer_internal(
|
||||
IN_INSTRUCTION_EXECUTOR.into(),
|
||||
address.into(),
|
||||
Balance { coin: Coin::Serai, amount: sri_balance },
|
||||
)?;
|
||||
}
|
||||
}
|
||||
DexCall::Swap(out_balance, out_address) => {
|
||||
let send_to_external = !out_address.is_native();
|
||||
let native_coin = out_balance.coin.is_native();
|
||||
|
||||
// we can't send native coin to external chain
|
||||
if native_coin && send_to_external {
|
||||
Err(Error::<T>::InvalidAddressForCoin)?;
|
||||
}
|
||||
|
||||
// mint the given coin on our account
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||
|
||||
// get the path
|
||||
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
||||
if !native_coin {
|
||||
path.push(out_balance.coin);
|
||||
}
|
||||
|
||||
// get the swap address
|
||||
// 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.
|
||||
let send_to = if send_to_external {
|
||||
IN_INSTRUCTION_EXECUTOR
|
||||
} else {
|
||||
out_address.clone().as_native().unwrap()
|
||||
};
|
||||
|
||||
// do the swap
|
||||
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||
origin.into(),
|
||||
BoundedVec::try_from(path).unwrap(),
|
||||
instruction.balance.amount.0,
|
||||
out_balance.amount.0,
|
||||
send_to.into(),
|
||||
)?;
|
||||
|
||||
// burn the received coins so that they sent back to the user
|
||||
// if it is requested to an external address.
|
||||
if send_to_external {
|
||||
// see how much we got
|
||||
let coin_balance =
|
||||
Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), out_balance.coin);
|
||||
let instruction = OutInstructionWithBalance {
|
||||
instruction: OutInstruction {
|
||||
address: out_address.as_external().unwrap(),
|
||||
// TODO: Properly pass data. Replace address with an OutInstruction entirely?
|
||||
data: None,
|
||||
},
|
||||
balance: Balance { coin: out_balance.coin, amount: coin_balance },
|
||||
};
|
||||
Coins::<T>::burn_non_sri(IN_INSTRUCTION_EXECUTOR.into(), instruction)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user