From a8440843081c9f2bc4cfa0b4a04465bba5da1c1e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 5 Nov 2023 06:41:20 -0500 Subject: [PATCH] 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 --- .github/workflows/tests.yml | 3 + Cargo.lock | 2 - deny.toml | 5 +- substrate/client/src/serai/dex.rs | 31 +- substrate/client/src/serai/mod.rs | 12 +- substrate/client/tests/common/dex.rs | 16 - substrate/client/tests/dex.rs | 521 +++++++++--------- substrate/coins/pallet/src/lib.rs | 16 +- substrate/coins/primitives/src/lib.rs | 2 +- substrate/dex/pallet/Cargo.toml | 64 ++- substrate/dex/pallet/src/benchmarking.rs | 195 ++----- substrate/dex/pallet/src/lib.rs | 108 ++-- substrate/dex/pallet/src/mock.rs | 34 +- substrate/dex/pallet/src/tests.rs | 192 +++---- substrate/dex/primitives/src/lib.rs | 21 +- substrate/in-instructions/pallet/Cargo.toml | 16 +- substrate/in-instructions/pallet/src/lib.rs | 38 +- .../in-instructions/primitives/Cargo.toml | 3 +- .../in-instructions/primitives/src/lib.rs | 31 +- .../primitives/src/shorthand.rs | 4 +- substrate/node/src/chain_spec.rs | 6 +- substrate/runtime/src/lib.rs | 25 +- substrate/validator-sets/pallet/src/lib.rs | 2 +- 23 files changed, 529 insertions(+), 818 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ca433477..46da342cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,9 @@ jobs: -p serai-primitives \ -p serai-coins-primitives \ -p serai-coins-pallet \ + -p serai-liquidity-tokens-primitives \ + -p serai-dex-primitives \ + -p serai-dex-pallet \ -p serai-validator-sets-primitives \ -p serai-validator-sets-pallet \ -p serai-in-instructions-primitives \ diff --git a/Cargo.lock b/Cargo.lock index 2c1ed319e..890d040de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8301,7 +8301,6 @@ dependencies = [ "parity-scale-codec", "scale-info", "serai-coins-pallet", - "serai-dex-pallet", "serai-in-instructions-primitives", "serai-primitives", "serai-validator-sets-pallet", @@ -8309,7 +8308,6 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", "thiserror", ] diff --git a/deny.toml b/deny.toml index b66357421..54b2b1f7f 100644 --- a/deny.toml +++ b/deny.toml @@ -55,10 +55,9 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "serai-coordinator" }, { allow = ["AGPL-3.0"], name = "serai-coins-pallet" }, - - { allow = ["AGPL-3.0"], name = "serai-dex-pallet" }, - { allow = ["AGPL-3.0"], name = "serai-dex-primitives" }, { 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" }, diff --git a/substrate/client/src/serai/dex.rs b/substrate/client/src/serai/dex.rs index dd79ebbfa..698fecfbe 100644 --- a/substrate/client/src/serai/dex.rs +++ b/substrate/client/src/serai/dex.rs @@ -1,12 +1,12 @@ +use sp_core::bounded_vec::BoundedVec; use serai_runtime::{ primitives::{SeraiAddress, Amount, Coin}, dex, Dex, Runtime, }; use subxt::tx::Payload; -use sp_core::bounded_vec::BoundedVec; -use crate::{TemporalSerai, SeraiError, Composite, scale_composite}; +use crate::{SeraiError, Composite, TemporalSerai, scale_composite}; const PALLET: &str = "Dex"; @@ -16,28 +16,9 @@ pub type DexEvent = dex::Event; pub struct SeraiDex<'a>(pub(crate) TemporalSerai<'a>); impl<'a> SeraiDex<'a> { pub async fn all_events(&self) -> Result, SeraiError> { - self - .0 - .events::(|event| { - matches!( - event, - DexEvent::PoolCreated { .. } | - DexEvent::LiquidityAdded { .. } | - DexEvent::SwapExecuted { .. } | - DexEvent::LiquidityRemoved { .. } | - DexEvent::Transfer { .. } - ) - }) - .await + self.0.events::(|_| true).await } - pub fn create_pool(coin: Coin) -> Payload> { - Payload::new( - PALLET, - "create_pool", - scale_composite(dex::Call::::create_pool { coin1: coin, coin2: Coin::Serai }), - ) - } pub fn add_liquidity( coin: Coin, coin_amount: Amount, @@ -69,11 +50,11 @@ impl<'a> SeraiDex<'a> { address: SeraiAddress, ) -> Payload> { let path = if to_coin.is_native() { - BoundedVec::truncate_from(vec![from_coin, Coin::Serai]) + BoundedVec::try_from(vec![from_coin, Coin::Serai]).unwrap() } else if from_coin.is_native() { - BoundedVec::truncate_from(vec![Coin::Serai, to_coin]) + BoundedVec::try_from(vec![Coin::Serai, to_coin]).unwrap() } else { - BoundedVec::truncate_from(vec![from_coin, Coin::Serai, to_coin]) + BoundedVec::try_from(vec![from_coin, Coin::Serai, to_coin]).unwrap() }; Payload::new( diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index e3c218a5d..d14caba49 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -35,10 +35,10 @@ use serai_runtime::{ pub mod coins; pub use coins::SeraiCoins; -pub mod in_instructions; -pub use in_instructions::SeraiInInstructions; pub mod dex; pub use dex::SeraiDex; +pub mod in_instructions; +pub use in_instructions::SeraiInInstructions; pub mod validator_sets; pub use validator_sets::SeraiValidatorSets; @@ -349,14 +349,14 @@ impl<'a> TemporalSerai<'a> { SeraiCoins(self) } - pub fn in_instructions(self) -> SeraiInInstructions<'a> { - SeraiInInstructions(self) - } - pub fn dex(self) -> SeraiDex<'a> { SeraiDex(self) } + pub fn in_instructions(self) -> SeraiInInstructions<'a> { + SeraiInInstructions(self) + } + pub fn validator_sets(self) -> SeraiValidatorSets<'a> { SeraiValidatorSets(self) } diff --git a/substrate/client/tests/common/dex.rs b/substrate/client/tests/common/dex.rs index 5cce42c34..295a0e732 100644 --- a/substrate/client/tests/common/dex.rs +++ b/substrate/client/tests/common/dex.rs @@ -7,22 +7,6 @@ use subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder; use crate::common::{serai, tx::publish_tx}; -#[allow(dead_code)] -pub async fn create_pool(coin: Coin, nonce: u32, pair: Pair) -> [u8; 32] { - let serai = serai().await; - - let tx = serai - .sign( - &PairSigner::new(pair), - &SeraiDex::create_pool(coin), - nonce, - BaseExtrinsicParamsBuilder::new(), - ) - .unwrap(); - - publish_tx(&tx).await -} - #[allow(dead_code)] pub async fn add_liquidity( coin: Coin, diff --git a/substrate/client/tests/dex.rs b/substrate/client/tests/dex.rs index b805f5a63..9870f5821 100644 --- a/substrate/client/tests/dex.rs +++ b/substrate/client/tests/dex.rs @@ -21,29 +21,225 @@ mod common; use common::{ serai, in_instructions::{provide_batch, mint_coin}, - dex::{ - create_pool as common_create_pool, add_liquidity as common_add_liquidity, swap as common_swap, - }, + dex::{add_liquidity as common_add_liquidity, swap as common_swap}, }; -pub fn decode_hex(s: &str) -> Vec { - (0 .. s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i .. i + 2], 16).unwrap()).collect() -} - +// TODO: Calculate all constants in the following tests +// TODO: Check LP token, coin balances +// TODO: Modularize common code +// TODO: Check Transfer events serai_test!( - async fn add_liquidity_in_instructions() { - let coin = Coin::Bitcoin; + create_pool: (|serai: Serai| async move { + let block = serai.block_by_number(0).await.unwrap().unwrap().hash(); + let events = serai.as_of(block).dex().all_events().await.unwrap(); + + assert_eq!( + 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, + }, + DexEvent::PoolCreated { + pool_id: (Coin::Serai, Coin::Ether), + pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Ether).encode())), + lp_token: 1, + }, + DexEvent::PoolCreated { + pool_id: (Coin::Serai, Coin::Dai), + pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Dai).encode())), + lp_token: 2, + }, + DexEvent::PoolCreated { + pool_id: (Coin::Serai, Coin::Monero), + pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Monero).encode())), + lp_token: 3, + }, + ] + ); + }) + + add_liquidity: (|serai: Serai| async move { + let coin = Coin::Monero; let pair = insecure_pair_from_name("Ferdie"); - let serai = serai().await; - let mut batch_id = 0; - // make the pool first so that we can add liquidity to it. - common_create_pool(coin, 0, pair.clone()).await; + // mint sriXMR in the account so that we can add liq. + // Ferdie account is already pre-funded with SRI. + mint_coin( + &serai, + Balance { coin, amount: Amount(100_000_000_000_000) }, + NetworkId::Monero, + 0, + pair.clone().public().into(), + ) + .await; + + // add liquidity + let coin_amount = Amount(50_000_000_000_000); + let sri_amount = Amount(50_000_000_000_000); + let block = common_add_liquidity(&serai, + coin, + coin_amount, + sri_amount, + 0, + pair.clone() + ).await; + // get only the add liq events + let mut events = serai.as_of(block).dex().all_events().await.unwrap(); + events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. })); + + assert_eq!( + events, + 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, + lp_token_minted: 49_999999990000 + }] + ); + }) + + // Tests coin -> SRI and SRI -> coin swaps. + swap_coin_to_sri: (|serai: Serai| async move { + let coin = Coin::Ether; + let pair = insecure_pair_from_name("Ferdie"); // mint sriXMR in the account so that we can add liq. // Ferdie account is already pre-funded with SRI. mint_coin( - Balance { coin, amount: Amount(100_000000000000) }, + &serai, + Balance { coin, amount: Amount(100_000_000_000_000) }, + NetworkId::Ethereum, + 0, + pair.clone().public().into(), + ) + .await; + + // add liquidity + common_add_liquidity(&serai, + coin, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 0, + pair.clone() + ).await; + + // now that we have our liquid pool, swap some coin to SRI. + let mut amount_in = Amount(25_000_000_000_000); + let mut block = common_swap(&serai, coin, Coin::Serai, amount_in, Amount(1), 1, pair.clone()) + .await; + + // get only the swap events + let mut events = serai.as_of(block).dex().all_events().await.unwrap(); + events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); + + let mut path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap(); + assert_eq!( + events, + vec![DexEvent::SwapExecuted { + who: pair.clone().public(), + send_to: pair.public(), + path, + amount_in: amount_in.0, + amount_out: 16633299966633 + }] + ); + + // now swap some SRI to coin + amount_in = Amount(10_000_000_000_000); + block = common_swap(&serai, Coin::Serai, coin, amount_in, Amount(1), 2, pair.clone()).await; + + // get only the swap events + let mut events = serai.as_of(block).dex().all_events().await.unwrap(); + events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); + + path = BoundedVec::try_from(vec![Coin::Serai, coin]).unwrap(); + assert_eq!( + events, + vec![DexEvent::SwapExecuted { + who: pair.clone().public(), + send_to: pair.public(), + path, + amount_in: amount_in.0, + amount_out: 17254428681101 + }] + ); + }) + + swap_coin_to_coin: (|serai: Serai| async move { + let coin1 = Coin::Monero; + let coin2 = Coin::Dai; + let pair = insecure_pair_from_name("Ferdie"); + + // mint coins + mint_coin( + &serai, + Balance { coin: coin1, amount: Amount(100_000_000_000_000) }, + NetworkId::Monero, + 0, + pair.clone().public().into(), + ) + .await; + mint_coin( + &serai, + Balance { coin: coin2, amount: Amount(100_000_000_000_000) }, + NetworkId::Ethereum, + 0, + pair.clone().public().into(), + ) + .await; + + // add liquidity to pools + common_add_liquidity(&serai, + coin1, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 0, + pair.clone() + ).await; + common_add_liquidity(&serai, + coin2, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 1, + pair.clone() + ).await; + + // swap coin1 -> coin2 + let amount_in = Amount(25_000_000_000_000); + let block = common_swap(&serai, coin1, coin2, amount_in, Amount(1), 2, pair.clone()).await; + + // get only the swap events + let mut events = serai.as_of(block).dex().all_events().await.unwrap(); + events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); + + let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap(); + assert_eq!( + events, + vec![DexEvent::SwapExecuted { + who: pair.clone().public(), + send_to: pair.public(), + path, + amount_in: amount_in.0, + amount_out: 12453103964435, + }] + ); + }) + + add_liquidity_in_instructions: (|serai: Serai| async move { + let coin = Coin::Bitcoin; + let pair = insecure_pair_from_name("Ferdie"); + let mut batch_id = 0; + + // mint sriBTC in the account so that we can add liq. + // Ferdie account is already pre-funded with SRI. + mint_coin( + &serai, + Balance { coin, amount: Amount(100_000_000_000_000) }, NetworkId::Bitcoin, batch_id, pair.clone().public().into(), @@ -52,11 +248,16 @@ serai_test!( batch_id += 1; // add liquidity - common_add_liquidity(coin, Amount(50_000000000000), Amount(50_000000000000), 1, pair.clone()) - .await; + common_add_liquidity(&serai, + coin, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 0, + pair.clone() + ).await; - // now that we have our liquid Btc/SRI pool, we can try add - // more liquidity to it + // now that we have our liquid SRI/BTC pool, we can add more liquidity to it via an + // InInstruction let mut block_hash = BlockHash([0; 32]); OsRng.fill_bytes(&mut block_hash.0); let batch = Batch { @@ -64,33 +265,27 @@ serai_test!( id: batch_id, block: block_hash, instructions: vec![InInstructionWithBalance { - instruction: InInstruction::Dex(DexCall::AddLiquidity(pair.public().into())), - balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000000000000) }, + instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())), + balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000_000_000_000) }, }], }; let block = provide_batch(batch).await; let mut events = serai.as_of(block).dex().all_events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. })); - - // we should have only 1 liq added event. - assert_eq!(events.len(), 1); - assert_eq!( events, vec![DexEvent::LiquidityAdded { who: IN_INSTRUCTION_EXECUTOR.into(), mint_to: pair.public(), pool_id: (Coin::Serai, Coin::Bitcoin), - amount1_provided: 6947918403646, - amount2_provided: 10_000000000000, // half of sended amount + amount1_provided: 6_947_918_403_646, + amount2_provided: 10_000_000_000_000, // half of sent amount lp_token: 0, lp_token_minted: 8333333333332 }] ); - - // TODO: get the minted lp token and check for balance of lp token on the address? - } + }) async fn swap_in_instructions() { let coin1 = Coin::Monero; @@ -100,13 +295,10 @@ serai_test!( let mut coin1_batch_id = 0; let mut coin2_batch_id = 0; - // create pools - common_create_pool(coin1, 0, pair.clone()).await; - common_create_pool(coin2, 1, pair.clone()).await; - // mint coins mint_coin( - Balance { coin: coin1, amount: Amount(100_000000000000) }, + &serai, + Balance { coin: coin1, amount: Amount(100_000_000_000_000) }, NetworkId::Monero, coin1_batch_id, pair.clone().public().into(), @@ -114,7 +306,8 @@ serai_test!( .await; coin1_batch_id += 1; mint_coin( - Balance { coin: coin2, amount: Amount(100_000000000000) }, + &serai, + Balance { coin: coin2, amount: Amount(100_000_000_000_000) }, NetworkId::Ethereum, coin2_batch_id, pair.clone().public().into(), @@ -123,16 +316,26 @@ serai_test!( coin2_batch_id += 1; // add liquidity to pools - common_add_liquidity(coin1, Amount(50_000000000000), Amount(50_000000000000), 2, pair.clone()) - .await; - common_add_liquidity(coin2, Amount(50_000000000000), Amount(50_000000000000), 3, pair.clone()) - .await; + common_add_liquidity(&serai, + coin1, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 0, + pair.clone() + ).await; + common_add_liquidity(&serai, + coin2, + Amount(50_000_000_000_000), + Amount(50_000_000_000_000), + 1, + pair.clone() + ).await; // rand address bytes let mut rand_bytes = vec![0; 32]; OsRng.fill_bytes(&mut rand_bytes); - // coin -> coin(XMR -> ETH) + // XMR -> ETH { // make an out address let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap()); @@ -149,7 +352,7 @@ serai_test!( block: block_hash, instructions: vec![InInstructionWithBalance { instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)), - balance: Balance { coin: coin1, amount: Amount(20_000000000000) }, + balance: Balance { coin: coin1, amount: Amount(20_000_000_000_000) }, }], }; @@ -158,23 +361,20 @@ serai_test!( let mut events = serai.as_of(block).dex().all_events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai, coin2]); + let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: IN_INSTRUCTION_EXECUTOR.into(), path, - amount_in: 20_000000000000, + amount_in: 20_000_000_000_000, amount_out: 11066655622377 }] ); } - // coin -> coin(with internal address, ETH -> XMR) + // ETH -> sriXMR { // make an out address let out_address = @@ -192,7 +392,7 @@ serai_test!( block: block_hash, instructions: vec![InInstructionWithBalance { instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())), - balance: Balance { coin: coin2, amount: Amount(20_000000000000) }, + balance: Balance { coin: coin2, amount: Amount(20_000_000_000_000) }, }], }; @@ -200,23 +400,20 @@ serai_test!( let mut events = serai.as_of(block).dex().all_events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - let path = BoundedVec::truncate_from(vec![coin2, Coin::Serai, coin1]); + let path = BoundedVec::try_from(vec![coin2, Coin::Serai, coin1]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: out_address.as_native().unwrap().into(), path, - amount_in: 20_000000000000, + amount_in: 20_000_000_000_000, amount_out: 26440798801319 }] ); } - // coin -> SRI(XMR -> SRI) + // XMR -> SRI { // make an out address let out_address = OutAddress::Serai(SeraiAddress::new(rand_bytes.try_into().unwrap())); @@ -233,7 +430,7 @@ serai_test!( block: block_hash, instructions: vec![InInstructionWithBalance { instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())), - balance: Balance { coin: coin1, amount: Amount(10_000000000000) }, + balance: Balance { coin: coin1, amount: Amount(10_000_000_000_000) }, }], }; @@ -241,225 +438,17 @@ serai_test!( let mut events = serai.as_of(block).dex().all_events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai]); + let path = BoundedVec::try_from(vec![coin1, Coin::Serai]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: out_address.as_native().unwrap().into(), path, - amount_in: 10_000000000000, + amount_in: 10_000_000_000_000, amount_out: 10711005507065 }] ); } - - // TODO: check balances? - } - - async fn create_pool() { - let coin = Coin::Bitcoin; - let pair = insecure_pair_from_name("Ferdie"); - - let pool_id = (Coin::Serai, coin); - let pool_account = PublicKey::from_raw(blake2_256(&pool_id.encode())); - - let block = common_create_pool(coin, 0, pair.clone()).await; - - let serai = serai().await; - let events = serai.as_of(block).dex().all_events().await.unwrap(); - - assert_eq!( - events, - vec![DexEvent::PoolCreated { - creator: pair.public(), - pool_id, - pool_account, - lp_token: 0 // first lp token that is created in the chain - }] - ); - } - - async fn add_liquidity() { - let coin = Coin::Monero; - let pair = insecure_pair_from_name("Ferdie"); - let serai = serai().await; - - // make the pool first so that we can add liquidity to it. - common_create_pool(coin, 0, pair.clone()).await; - - // mint sriXMR in the account so that we can add liq. - // Ferdie account is already pre-funded with SRI. - mint_coin( - Balance { coin, amount: Amount(100_000000000000) }, - NetworkId::Monero, - 0, - pair.clone().public().into(), - ) - .await; - - // add liquidity - let coin_amount = Amount(50_000000000000); - let sri_amount = Amount(50_000000000000); - let block = common_add_liquidity(coin, coin_amount, sri_amount, 1, pair.clone()).await; - - // get only the add liq events - let mut events = serai.as_of(block).dex().all_events().await.unwrap(); - events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. })); - - // we should have only 1 liq added event. - assert_eq!(events.len(), 1); - - assert_eq!( - events, - 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: 0, - // TODO: how to calculate this? just looks like 50 - 0.00000001. - // Why that fraction was subtracted? - lp_token_minted: 49_999999990000 - }] - ); - } - - // Tests coin -> SRI and SRI -> coin - // swaps. - async fn swap_coin_to_sri() { - let coin = Coin::Ether; - let pair = insecure_pair_from_name("Ferdie"); - let serai = serai().await; - - // make the pool first so that we can add liquidity to it. - common_create_pool(coin, 0, pair.clone()).await; - - // mint sriXMR in the account so that we can add liq. - // Ferdie account is already pre-funded with SRI. - mint_coin( - Balance { coin, amount: Amount(100_000000000000) }, - NetworkId::Ethereum, - 0, - pair.clone().public().into(), - ) - .await; - - // add liquidity - common_add_liquidity(coin, Amount(50_000000000000), Amount(50_000000000000), 1, pair.clone()) - .await; - - // now that we have our liquid pool, swap some coin to SRI. - let mut amount_in = Amount(25_000000000000); - let mut block = common_swap(coin, Coin::Serai, amount_in, Amount(1), 2, pair.clone()).await; - - // get only the swap events - let mut events = serai.as_of(block).dex().all_events().await.unwrap(); - events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - let mut path = BoundedVec::truncate_from(vec![coin, Coin::Serai]); - assert_eq!( - events, - vec![DexEvent::SwapExecuted { - who: pair.clone().public(), - send_to: pair.public(), - path, - amount_in: amount_in.0, - // TODO: again how to know this? This number is taken from the event itself. - // The pool had 1:1 liquidity but seems like it favored SRI to be more - // expensive? - amount_out: 16633299966633 - }] - ); - - // now swap some SRI to coin - amount_in = Amount(10_000000000000); - block = common_swap(Coin::Serai, coin, amount_in, Amount(1), 3, pair.clone()).await; - - // get only the swap events - let mut events = serai.as_of(block).dex().all_events().await.unwrap(); - events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - path = BoundedVec::truncate_from(vec![Coin::Serai, coin]); - assert_eq!( - events, - vec![DexEvent::SwapExecuted { - who: pair.clone().public(), - send_to: pair.public(), - path, - amount_in: amount_in.0, - // TODO: again this? - amount_out: 17254428681101 - }] - ); - - // TODO: check the balance of the account? - } - - async fn swap_coin_to_coin() { - let coin1 = Coin::Monero; - let coin2 = Coin::Dai; - let pair = insecure_pair_from_name("Ferdie"); - let serai = serai().await; - - // create pools - common_create_pool(coin1, 0, pair.clone()).await; - common_create_pool(coin2, 1, pair.clone()).await; - - // mint coins - mint_coin( - Balance { coin: coin1, amount: Amount(100_000000000000) }, - NetworkId::Monero, - 0, - pair.clone().public().into(), - ) - .await; - mint_coin( - Balance { coin: coin2, amount: Amount(100_000000000000) }, - NetworkId::Ethereum, - 0, - pair.clone().public().into(), - ) - .await; - - // add liquidity to pools - common_add_liquidity(coin1, Amount(50_000000000000), Amount(50_000000000000), 2, pair.clone()) - .await; - common_add_liquidity(coin2, Amount(50_000000000000), Amount(50_000000000000), 3, pair.clone()) - .await; - - // swap coin1 -> coin2 - let amount_in = Amount(25_000000000000); - let block = common_swap(coin1, coin2, amount_in, Amount(1), 4, pair.clone()).await; - - // get only the swap events - let mut events = serai.as_of(block).dex().all_events().await.unwrap(); - events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); - - // we should have only 1 swap event. - assert_eq!(events.len(), 1); - - let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai, coin2]); - assert_eq!( - events, - vec![DexEvent::SwapExecuted { - who: pair.clone().public(), - send_to: pair.public(), - path, - amount_in: amount_in.0, - // TODO: again this? - amount_out: 12453103964435 - }] - ); - } + }) ); diff --git a/substrate/coins/pallet/src/lib.rs b/substrate/coins/pallet/src/lib.rs index 646a8bcea..433c2bd7f 100644 --- a/substrate/coins/pallet/src/lib.rs +++ b/substrate/coins/pallet/src/lib.rs @@ -21,7 +21,7 @@ pub mod pallet { use primitives::*; #[pallet::config] - pub trait Config: frame_system::Config + TpConfig { + pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -74,12 +74,13 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { // initialize the supply of the coins - for c in COINS.iter() { + // TODO: Don't use COINS yet GenesisConfig so we can safely expand COINS + for c in &COINS { Supply::::set(c, 0); } // initialize the genesis accounts - for (account, balance) in self.accounts.iter() { + for (account, balance) in &self.accounts { Pallet::::mint(*account, *balance).unwrap(); } } @@ -244,10 +245,12 @@ pub mod pallet { } } + // TODO: Have DEX implement for Coins, not Coins implement for Coins impl CoinsTrait for Pallet { 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 } @@ -256,6 +259,7 @@ pub mod pallet { 1 } + // TODO: Move coin next to amount fn transfer( coin: Self::CoinId, from: &Public, @@ -267,6 +271,7 @@ pub mod pallet { Ok(amount) } + // TODO: Move coin next to amount fn mint( coin: Self::CoinId, to: &Public, @@ -277,7 +282,10 @@ pub mod pallet { } } - impl OnChargeTransaction for Pallet { + impl OnChargeTransaction for Pallet + where + T: TpConfig, + { type Balance = SubstrateAmount; type LiquidityInfo = Option; diff --git a/substrate/coins/primitives/src/lib.rs b/substrate/coins/primitives/src/lib.rs index 911a4b6f4..45eefa6b3 100644 --- a/substrate/coins/primitives/src/lib.rs +++ b/substrate/coins/primitives/src/lib.rs @@ -12,7 +12,7 @@ use scale_info::TypeInfo; use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address}; -pub const FEE_ACCOUNT: SeraiAddress = system_address(b"FeeAccount"); +pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees"); #[derive( Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo, diff --git a/substrate/dex/pallet/Cargo.toml b/substrate/dex/pallet/Cargo.toml index 6d56e4311..cff31531c 100644 --- a/substrate/dex/pallet/Cargo.toml +++ b/substrate/dex/pallet/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "serai-dex-pallet" version = "0.1.0" -description = "Dex pallet for Serai" +description = "DEX pallet for Serai" license = "AGPL-3.0-only" repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex/pallet" authors = ["Parity Technologies , Akil Demir "] @@ -10,62 +10,66 @@ 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 } -frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } -frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } -frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } -sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false } -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-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-arithmetic = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-api = { 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 } +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 } -pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } -serai-primitives = { path = "../../primitives", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", "scale-info/std", - "coins-pallet/std", - "liquidity-tokens-pallet/std", - "pallet-transaction-payment/std", - - "dex-primitives/std", - "serai-primitives/std", - - "sp-api/std", + "sp-std/std", "sp-arithmetic/std", - "sp-core/std", "sp-io/std", + "sp-api/std", "sp-runtime/std", - "sp-std/std", + "sp-core/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 = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "dex-primitives/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", "sp-runtime/try-runtime", + + "frame-system/try-runtime", + "frame-support/try-runtime", ] diff --git a/substrate/dex/pallet/src/benchmarking.rs b/substrate/dex/pallet/src/benchmarking.rs index cb5f9e43d..372c9df8d 100644 --- a/substrate/dex/pallet/src/benchmarking.rs +++ b/substrate/dex/pallet/src/benchmarking.rs @@ -24,7 +24,6 @@ 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_core::Get; use sp_runtime::traits::{Bounded, StaticLookup}; use sp_std::{ops::Div, prelude::*}; @@ -70,27 +69,16 @@ where T::Coins: Coins, T::PoolCoinId: Into, { - let (_, _) = create_coin::(coin1); + assert_eq!(coin1, &T::MultiCoinIdConverter::get_native()); + let (caller, caller_lookup) = create_coin::(coin2); - assert_ok!(Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - coin1.clone(), - coin2.clone() - )); + assert_ok!(Dex::::create_pool(coin2.clone())); let lp_token = get_lp_token_id::(); (lp_token, caller, caller_lookup) } -fn assert_last_event(generic_event: ::RuntimeEvent) { - let events = frame_system::Pallet::::events(); - let system_event: ::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); -} - benchmarks! { where_clause { where @@ -101,22 +89,6 @@ benchmarks! { T::PoolCoinId: Into, } - create_pool { - let coin1 = T::MultiCoinIdConverter::get_native(); - let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into(); - let (caller, _) = create_coin::(&coin2); - }: _(SystemOrigin::Signed(caller.clone()), coin1.clone(), coin2.clone()) - verify { - let lp_token = get_lp_token_id::(); - let pool_id = (coin1.clone(), coin2.clone()); - assert_last_event::(Event::PoolCreated { - creator: caller.clone(), - pool_account: Dex::::get_pool_account(&pool_id), - pool_id, - lp_token, - }.into()); - } - add_liquidity { let coin1 = T::MultiCoinIdConverter::get_native(); let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into(); @@ -220,79 +192,31 @@ benchmarks! { caller.clone(), )?; - let path; - let swap_amount; - // if we only allow the native-coin pools, then the worst case scenario would be to swap - // coin1-native-coin2 - if !T::AllowMultiCoinPools::get() { - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - coin2.clone() - )?; - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - coin2.clone(), - (500 * ed).into(), - 1000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - path = vec![coin1.clone(), native.clone(), coin2.clone()]; - swap_amount = 100.into(); - } else { - let coin3: T::MultiCoinId = T::BenchmarkHelper::coin_id(3).into(); - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - coin1.clone(), - coin2.clone() - )?; - let (_, _) = create_coin::(&coin3); - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - coin2.clone(), - coin3.clone() - )?; + let swap_amount = 100.into(); - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - coin1.clone(), - coin2.clone(), - 200.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - coin2.clone(), - coin3.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - path = vec![native.clone(), coin1.clone(), coin2.clone(), coin3.clone()]; - swap_amount = (ed + ed_bump).into(); - } + // since we only allow the native-coin pools, then the worst case scenario would be to swap + // coin1-native-coin2 + Dex::::create_pool(coin2.clone())?; + Dex::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + coin2.clone(), + (500 * ed).into(), + 1000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; - let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); + let path = vec![coin1.clone(), native.clone(), coin2.clone()]; + 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()) verify { let ed_bump = 2u64; - if !T::AllowMultiCoinPools::get() { - let new_coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller); - assert_eq!(new_coin1_balance, coin1_balance - 100.into()); - } else { - let new_native_balance = T::Currency::balance(&caller); - assert_eq!(new_native_balance, native_balance - (ed + ed_bump).into()); - } + let new_coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller); + assert_eq!(new_coin1_balance, coin1_balance - 100.into()); } swap_tokens_for_exact_tokens { @@ -314,65 +238,23 @@ benchmarks! { caller.clone(), )?; - // if we only allow the native-coin pools, then the worst case scenario would be to swap + // since we only allow the native-coin pools, then the worst case scenario would be to swap // coin1-native-coin2 - let path = if !T::AllowMultiCoinPools::get() { - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - coin2.clone() - )?; - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - coin2.clone(), - (500 * ed).into(), - 1000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - vec![coin1.clone(), native.clone(), coin2.clone()] - } else { - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - coin1.clone(), - coin2.clone() - )?; - let coin3: T::MultiCoinId = T::BenchmarkHelper::coin_id(3).into(); - let (_, _) = create_coin::(&coin3); - Dex::::create_pool( - SystemOrigin::Signed(caller.clone()).into(), - coin2.clone(), - coin3.clone() - )?; - - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - coin1.clone(), - coin2.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - Dex::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - coin2.clone(), - coin3.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - vec![native.clone(), coin1.clone(), coin2.clone(), coin3.clone()] - }; + Dex::::create_pool(coin2.clone())?; + Dex::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + coin2.clone(), + (500 * ed).into(), + 1000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + let path = vec![coin1.clone(), native.clone(), coin2.clone()]; let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); let coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller); - let coin3_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(3), &caller); }: _( SystemOrigin::Signed(caller.clone()), path.clone(), @@ -381,13 +263,8 @@ benchmarks! { caller.clone() ) verify { - if !T::AllowMultiCoinPools::get() { - let new_coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller); - assert_eq!(new_coin2_balance, coin2_balance + 100.into()); - } else { - let new_coin3_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(3), &caller); - assert_eq!(new_coin3_balance, coin3_balance + 100.into()); - } + let new_coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller); + assert_eq!(new_coin2_balance, coin2_balance + 100.into()); } impl_benchmark_test_suite!(Dex, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/dex/pallet/src/lib.rs b/substrate/dex/pallet/src/lib.rs index e4de4b52b..5f808da21 100644 --- a/substrate/dex/pallet/src/lib.rs +++ b/substrate/dex/pallet/src/lib.rs @@ -20,8 +20,7 @@ //! # Serai Dex pallet //! -//! Serai Dex pallet based on the [Uniswap V2](https://github.com/Uniswap/v2-core) -//! logic. +//! Serai Dex pallet based on the [Uniswap V2](https://github.com/Uniswap/v2-core) logic. //! //! ## Overview //! @@ -51,11 +50,14 @@ //! //! ```text //! curl -sS -H "Content-Type: application/json" -d \ -//! '{"id":1, "jsonrpc":"2.0", "method": "state_call", -//! "params": [ -//! "DexApi_quote_price_tokens_for_exact_tokens", -//! "0x0101000000000000000000000011000000000000000000" -//! ] +//! '{ +//! "id": 1, +//! "jsonrpc": "2.0", +//! "method": "state_call", +//! "params": [ +//! "DexApi_quote_price_tokens_for_exact_tokens", +//! "0x0101000000000000000000000011000000000000000000" +//! ] //! }' \ //! http://localhost:9933/ //! ``` @@ -99,8 +101,7 @@ pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, BoundedBTreeSet, PalletId}; - use sp_arithmetic::Permill; + use frame_support::{pallet_prelude::*, BoundedBTreeSet}; use sp_runtime::{ traits::{IntegerSquareRoot, One, Zero}, Saturating, @@ -143,14 +144,14 @@ pub mod pallet { /// Identifier for the class of non-native coin. /// Note: A `From` bound here would prevent `MultiLocation` from being used as an /// `CoinId`. - type CoinId: CoinId; + type CoinId: frame_support::Serialize + sp_runtime::DeserializeOwned + CoinId; /// Type that identifies either the native currency or a token class from `Coins`. /// `Ord` is added because of `get_pool_id`. /// /// The pool's `AccountId` is derived from this type. Any changes to the type may /// necessitate a migration. - type MultiCoinId: CoinId + Ord + From; + type MultiCoinId: Ord + CoinId + From; /// Type to convert an `CoinId` into `MultiCoinId`. type MultiCoinIdConverter: MultiCoinIdConverter; @@ -173,17 +174,6 @@ pub mod pallet { #[pallet::constant] type LPFee: Get; - /// A one-time fee to setup the pool. - #[pallet::constant] - type PoolSetupFee: Get; - - /// An account that receives the pool setup fee. - type PoolSetupFeeReceiver: Get; - - /// A fee to withdraw the liquidity. - #[pallet::constant] - type LiquidityWithdrawalFee: Get; - /// The minimum LP token amount that could be minted. Ameliorates rounding errors. #[pallet::constant] type MintMinLiquidity: Get; @@ -192,14 +182,6 @@ pub mod pallet { #[pallet::constant] type MaxSwapPathLength: Get; - /// The pallet's id, used for deriving its sovereign account ID. - #[pallet::constant] - type PalletId: Get; - - /// A setting to allow creating pools with both non-native coins. - #[pallet::constant] - type AllowMultiCoinPools: Get; - /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -225,8 +207,6 @@ pub mod pallet { pub enum Event { /// A successful call of the `CretaPool` extrinsic will create this event. PoolCreated { - /// The account that created the pool. - creator: T::AccountId, /// The pool id associated with the pool. Note that the order of the coins may not be /// the same as the order specified in the create pool extrinsic. pool_id: PoolIdOf, @@ -271,8 +251,6 @@ pub mod pallet { lp_token: T::PoolCoinId, /// The amount of lp tokens that were burned of that id. lp_token_burned: T::CoinBalance, - /// Liquidity withdrawal fee (%). - withdrawal_fee: Permill, }, /// Coins have been converted from one to another. Both `SwapExactTokenForToken` /// and `SwapTokenForExactToken` will generate this event. @@ -302,6 +280,28 @@ pub mod pallet { }, } + #[pallet::genesis_config] + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] + pub struct GenesisConfig { + /// Pools to create at launch. + pub pools: Vec, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { pools: Default::default() } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for coin in &self.pools { + Pallet::::create_pool(coin.clone().into()).unwrap(); + } + } + } + #[pallet::error] pub enum Error { /// Provided coins are equal. @@ -372,39 +372,22 @@ pub mod pallet { } } - /// Pallet's callable functions. - #[pallet::call] impl Pallet { /// Creates an empty liquidity pool and an associated new `lp_token` coin /// (the id of which is returned in the `Event::PoolCreated` event). /// /// Once a pool is created, someone may [`Pallet::add_liquidity`] to it. - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::create_pool())] - pub fn create_pool( - origin: OriginFor, - coin1: T::MultiCoinId, - coin2: T::MultiCoinId, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; + pub(crate) fn create_pool(coin2: T::MultiCoinId) -> DispatchResult { + let coin1 = T::MultiCoinIdConverter::get_native(); ensure!(coin1 != coin2, Error::::EqualCoins); // prepare pool_id let pool_id = Self::get_pool_id(coin1, coin2); ensure!(!Pools::::contains_key(&pool_id), Error::::PoolExists); - let (coin1, _) = &pool_id; - if !T::AllowMultiCoinPools::get() && !T::MultiCoinIdConverter::is_native(coin1) { - Err(Error::::PoolMustContainNativeCurrency)?; - } let pool_account = Self::get_pool_account(&pool_id); frame_system::Pallet::::inc_providers(&pool_account); - // pay the setup fee - // TODO: Since we don't have PoolSetupFee, remove this entirely? - // or we might need it in the future? - T::Currency::transfer(&sender, &T::PoolSetupFeeReceiver::get(), T::PoolSetupFee::get())?; - let lp_token = NextPoolCoinId::::get() .or(T::PoolCoinId::initial_value()) .ok_or(Error::::IncorrectPoolCoinId)?; @@ -414,11 +397,16 @@ pub mod pallet { let pool_info = PoolInfo { lp_token: lp_token.clone() }; Pools::::insert(pool_id.clone(), pool_info); - Self::deposit_event(Event::PoolCreated { creator: sender, pool_id, pool_account, lp_token }); + Self::deposit_event(Event::PoolCreated { pool_id, pool_account, lp_token }); Ok(()) } + } + /// Pallet's callable functions. + // TODO: For all of these calls, limit one of these to always be Coin::Serai + #[pallet::call] + impl Pallet { /// Provide liquidity into the pool of `coin1` and `coin2`. /// NOTE: an optimal amount of coin1 and coin2 will be calculated and /// might be different than the provided `amount1_desired`/`amount2_desired` @@ -428,7 +416,7 @@ pub mod pallet { /// /// Once liquidity is added, someone may successfully call /// [`Pallet::swap_exact_tokens_for_tokens`] successfully. - #[pallet::call_index(1)] + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_liquidity())] #[allow(clippy::too_many_arguments)] pub fn add_liquidity( @@ -527,7 +515,7 @@ pub mod pallet { /// Allows you to remove liquidity by providing the `lp_token_burn` tokens that will be /// burned in the process. With the usage of `amount1_min_receive`/`amount2_min_receive` /// it's possible to control the min amount of returned tokens you're happy with. - #[pallet::call_index(2)] + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_liquidity())] pub fn remove_liquidity( origin: OriginFor, @@ -559,8 +547,7 @@ pub mod pallet { let reserve2 = Self::get_balance(&pool_account, &coin2)?; let total_supply = T::PoolCoins::total_issuance(pool.lp_token.clone()); - let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn; - let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount); + let lp_redeem_amount = lp_token_burn; let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?; let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?; @@ -594,7 +581,6 @@ pub mod pallet { amount2, lp_token: pool.lp_token.clone(), lp_token_burned: lp_token_burn, - withdrawal_fee: T::LiquidityWithdrawalFee::get(), }); Ok(()) @@ -606,7 +592,7 @@ pub mod pallet { /// /// [`DexApi::quote_price_exact_tokens_for_tokens`] runtime call can be called /// for a quote. - #[pallet::call_index(3)] + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens())] pub fn swap_exact_tokens_for_tokens( origin: OriginFor, @@ -632,7 +618,7 @@ pub mod pallet { /// /// [`DexApi::quote_price_tokens_for_exact_tokens`] runtime call can be called /// for a quote. - #[pallet::call_index(4)] + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens())] pub fn swap_tokens_for_exact_tokens( origin: OriginFor, diff --git a/substrate/dex/pallet/src/mock.rs b/substrate/dex/pallet/src/mock.rs index 1dd951865..2376b6e10 100644 --- a/substrate/dex/pallet/src/mock.rs +++ b/substrate/dex/pallet/src/mock.rs @@ -24,13 +24,10 @@ use super::*; use crate as dex; use frame_support::{ - construct_runtime, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, ConstU8}, - weights::IdentityFee, - PalletId, + construct_runtime, + traits::{ConstU32, ConstU64}, }; -use sp_arithmetic::Permill; use sp_core::{H256, sr25519::Public}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, @@ -41,7 +38,6 @@ use serai_primitives::{Coin, Balance, Amount, system_address}; pub use coins_pallet as coins; pub use liquidity_tokens_pallet as liquidity_tokens; -pub use pallet_transaction_payment as transaction_payment; type Block = frame_system::mocking::MockBlock; @@ -49,7 +45,6 @@ construct_runtime!( pub enum Test { System: frame_system, - TransactionPayment: transaction_payment, CoinsPallet: coins, LiquidityTokens: liquidity_tokens, Dex: dex, @@ -82,15 +77,6 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } -impl transaction_payment::Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CoinsPallet; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type FeeMultiplierUpdate = (); -} - impl coins::Config for Test { type RuntimeEvent = RuntimeEvent; } @@ -99,17 +85,6 @@ impl liquidity_tokens::Config for Test { type RuntimeEvent = RuntimeEvent; } -parameter_types! { - pub const CoinConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub storage AllowMultiCoinPools: bool = true; - // should be non-zero if AllowMultiCoinPools is true, otherwise can be zero - pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); -} - -ord_parameter_types! { - pub const CoinConversionOrigin: Public = Public::from(system_address(b"py/ascon")); -} - pub struct CoinConverter; impl MultiCoinIdConverter for CoinConverter { /// Returns the MultiCoinId representing the native currency of the chain. @@ -140,13 +115,8 @@ impl Config for Test { type PoolCoinId = u32; type Coins = CoinsPallet; type PoolCoins = LiquidityTokens; - type PalletId = CoinConversionPalletId; type WeightInfo = (); type LPFee = ConstU32<3>; // means 0.3% - type PoolSetupFee = ConstU64<0>; // should be more or equal to the existential deposit - type PoolSetupFeeReceiver = CoinConversionOrigin; - type LiquidityWithdrawalFee = LiquidityWithdrawalFee; - type AllowMultiCoinPools = AllowMultiCoinPools; type MaxSwapPathLength = ConstU32<4>; // 100 is good enough when the main currency has 12 decimals. type MintMinLiquidity = ConstU64<100>; diff --git a/substrate/dex/pallet/src/tests.rs b/substrate/dex/pallet/src/tests.rs index e94a7f993..4b13dc26f 100644 --- a/substrate/dex/pallet/src/tests.rs +++ b/substrate/dex/pallet/src/tests.rs @@ -19,8 +19,7 @@ // Please check the current distribution for up-to-date copyright and licensing information. use crate::{mock::*, *}; -use frame_support::{assert_noop, assert_ok, traits::Get}; -use sp_arithmetic::Permill; +use frame_support::{assert_noop, assert_ok}; pub use coins_pallet as coins; pub use dex_primitives as primitives; @@ -104,18 +103,14 @@ fn can_create_pool() { let lp_token = Dex::get_next_pool_coin_id(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(1000) })); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(Dex::create_pool(token_2)); - let setup_fee = <::PoolSetupFee as Get<::Balance>>::get(); - let pool_account = <::PoolSetupFeeReceiver as Get>::get(); - assert_eq!(balance(user, Coin::native()), 1000 - (setup_fee + coin_account_deposit)); - assert_eq!(balance(pool_account, Coin::native()), setup_fee); + assert_eq!(balance(user, Coin::native()), 1000 - coin_account_deposit); assert_eq!(lp_token + 1, Dex::get_next_pool_coin_id()); assert_eq!( events(), [Event::::PoolCreated { - creator: user, pool_id, pool_account: Dex::get_pool_account(&pool_id), lp_token @@ -123,52 +118,21 @@ fn can_create_pool() { ); assert_eq!(pools(), vec![pool_id]); - assert_noop!( - Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_1), - Error::::EqualCoins - ); - assert_noop!( - Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_2), - Error::::EqualCoins - ); - - // validate we can create Coin(1)/Coin(2) pool - let token_1 = Coin::Bitcoin; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - - // validate we can force the first coin to be the Native currency only - AllowMultiCoinPools::set(&false); - let token_1 = Coin::Ether; - assert_noop!( - Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2), - Error::::PoolMustContainNativeCurrency - ); + assert_noop!(Dex::create_pool(token_1), Error::::EqualCoins); }); } #[test] fn create_same_pool_twice_should_fail() { new_test_ext().execute_with(|| { - let user: PublicKey = system_address(b"user1").into(); - let token_1 = Coin::native(); let token_2 = Coin::Dai; let lp_token = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(Dex::create_pool(token_2)); let expected_free = lp_token + 1; assert_eq!(expected_free, Dex::get_next_pool_coin_id()); - assert_noop!( - Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_1), - Error::::PoolExists - ); - assert_eq!(expected_free, Dex::get_next_pool_coin_id()); - - // Try switching the same tokens around: - assert_noop!( - Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2), - Error::::PoolExists - ); + assert_noop!(Dex::create_pool(token_2), Error::::PoolExists); assert_eq!(expected_free, Dex::get_next_pool_coin_id()); }); } @@ -176,7 +140,6 @@ fn create_same_pool_twice_should_fail() { #[test] fn different_pools_should_have_different_lp_tokens() { new_test_ext().execute_with(|| { - let user: PublicKey = system_address(b"user1").into(); let token_1 = Coin::native(); let token_2 = Coin::Bitcoin; let token_3 = Coin::Ether; @@ -184,24 +147,22 @@ fn different_pools_should_have_different_lp_tokens() { let pool_id_1_3 = (token_1, token_3); let lp_token2_1 = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(Dex::create_pool(token_2)); let lp_token3_1 = Dex::get_next_pool_coin_id(); assert_eq!( events(), [Event::::PoolCreated { - creator: user, pool_id: pool_id_1_2, pool_account: Dex::get_pool_account(&pool_id_1_2), lp_token: lp_token2_1 }] ); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_3, token_1)); + assert_ok!(Dex::create_pool(token_3)); assert_eq!( events(), [Event::::PoolCreated { - creator: user, pool_id: pool_id_1_3, pool_account: Dex::get_pool_account(&pool_id_1_3), lp_token: lp_token3_1, @@ -221,9 +182,9 @@ fn can_add_liquidity() { let token_3 = Coin::Monero; let lp_token1 = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); let lp_token2 = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + assert_ok!(Dex::create_pool(token_3)); let ed = get_ed(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(10000 * 2 + ed) })); @@ -296,30 +257,11 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { let token_1 = Coin::native(); let token_2 = Coin::Bitcoin; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(1000) })); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_2, amount: Amount(1000) })); - // TODO: Following test is stupid, hence it is commented out. - // it expects native coin existential deposit to be something more than atomic unit 1, - // Otherwise both cases are the same and they expect different outcomes. - // So first one fails,and second one passes. But if existential deposit is more than 1, - // both of them passes without a problem. Why would you force ED to be more than 1?? - // assert_noop!( - // Dex::add_liquidity( - // RuntimeOrigin::signed(user), - // token_1, - // token_2, - // 1, - // 1, - // 1, - // 1, - // user - // ), - // Error::::AmountOneLessThanMinimal - // ); - assert_noop!( Dex::add_liquidity(RuntimeOrigin::signed(user), token_1, token_2, get_ed(), 1, 1, 1, user), Error::::InsufficientLiquidityMinted @@ -335,8 +277,8 @@ fn add_tiny_liquidity_directly_to_pool_address() { let token_2 = Coin::Ether; let token_3 = Coin::Dai; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + assert_ok!(Dex::create_pool(token_2)); + assert_ok!(Dex::create_pool(token_3)); let ed = get_ed(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(10000 * 2 + ed) })); @@ -383,7 +325,7 @@ fn can_remove_liquidity() { let pool_id = (token_1, token_2); let lp_token = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(10000000000) })); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_2, amount: Amount(100000) })); @@ -400,7 +342,6 @@ fn can_remove_liquidity() { )); let total_lp_received = pool_balance(user, lp_token); - LiquidityWithdrawalFee::set(&Permill::from_percent(10)); assert_ok!(Dex::remove_liquidity( RuntimeOrigin::signed(user), @@ -416,20 +357,19 @@ fn can_remove_liquidity() { who: user, withdraw_to: user, pool_id, - amount1: 899991000, - amount2: 89999, + amount1: 999990000, + amount2: 99999, lp_token, lp_token_burned: total_lp_received, - withdrawal_fee: ::LiquidityWithdrawalFee::get() })); let pool_account = Dex::get_pool_account(&pool_id); - assert_eq!(balance(pool_account, token_1), 100009000); - assert_eq!(balance(pool_account, token_2), 10001); + assert_eq!(balance(pool_account, token_1), 10000); + assert_eq!(balance(pool_account, token_2), 1); assert_eq!(pool_balance(pool_account, lp_token), 100); - assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 899991000); - assert_eq!(balance(user, token_2), 89999); + assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 999990000); + assert_eq!(balance(user, token_2), 99999); assert_eq!(pool_balance(user, lp_token), 0); }); } @@ -442,7 +382,7 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() { let token_2 = Coin::Dai; let lp_token = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint( user, @@ -486,7 +426,7 @@ fn can_quote_price() { let token_1 = Coin::native(); let token_2 = Coin::Ether; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(100000) })); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_2, amount: Amount(1000) })); @@ -626,7 +566,7 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() { let token_1 = Coin::native(); let token_2 = Coin::Bitcoin; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(100000) })); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_2, amount: Amount(1000) })); @@ -672,7 +612,7 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() { let token_1 = Coin::native(); let token_2 = Coin::Monero; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(100000) })); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_2, amount: Amount(1000) })); @@ -721,7 +661,7 @@ fn can_swap_with_native() { let token_2 = Coin::Ether; let pool_id = (token_1, token_2); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); let ed = get_ed(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(10000 + ed) })); @@ -765,32 +705,32 @@ fn can_swap_with_realistic_values() { new_test_ext().execute_with(|| { let user = system_address(b"user1").into(); let sri = Coin::native(); - let usd = Coin::Dai; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), sri, usd)); + let dai = Coin::Dai; + assert_ok!(Dex::create_pool(dai)); const UNIT: u64 = 1_000_000_000; assert_ok!(CoinsPallet::mint(user, Balance { coin: sri, amount: Amount(300_000 * UNIT) })); - assert_ok!(CoinsPallet::mint(user, Balance { coin: usd, amount: Amount(1_100_000 * UNIT) })); + assert_ok!(CoinsPallet::mint(user, Balance { coin: dai, amount: Amount(1_100_000 * UNIT) })); let liquidity_dot = 200_000 * UNIT; // ratio for a 5$ price - let liquidity_usd = 1_000_000 * UNIT; + let liquidity_dai = 1_000_000 * UNIT; assert_ok!(Dex::add_liquidity( RuntimeOrigin::signed(user), sri, - usd, + dai, liquidity_dot, - liquidity_usd, + liquidity_dai, 1, 1, user, )); - let input_amount = 10 * UNIT; // usd + let input_amount = 10 * UNIT; // dai assert_ok!(Dex::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![usd, sri], + bvec![dai, sri], input_amount, 1, user, @@ -799,7 +739,7 @@ fn can_swap_with_realistic_values() { assert!(events().contains(&Event::::SwapExecuted { who: user, send_to: user, - path: bvec![usd, sri], + path: bvec![dai, sri], amount_in: 10 * UNIT, // usd amount_out: 1_993_980_120, // About 2 dot after div by UNIT. })); @@ -813,7 +753,7 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() { let token_1 = Coin::native(); let token_2 = Coin::Monero; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); // Check can't swap an empty pool assert_noop!( @@ -838,7 +778,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { let pool_id = (token_1, token_2); let lp_token = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); let ed = get_ed(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(10000 + ed) })); @@ -951,7 +891,7 @@ fn swap_should_not_work_if_too_much_slippage() { let token_1 = Coin::native(); let token_2 = Coin::Ether; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint( user, @@ -996,7 +936,7 @@ fn can_swap_tokens_for_exact_tokens() { let token_2 = Coin::Dai; let pool_id = (token_1, token_2); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); let ed = get_ed(); assert_ok!(CoinsPallet::mint(user, Balance { coin: token_1, amount: Amount(20000 + ed) })); @@ -1054,7 +994,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { let pool_id = (token_1, token_2); let lp_token = Dex::get_next_pool_coin_id(); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); let ed = get_ed(); let base1 = 10000; @@ -1136,7 +1076,7 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { let token_1 = Coin::native(); let token_2 = Coin::Ether; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); assert_ok!(CoinsPallet::mint( user, @@ -1181,8 +1121,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { let token_2 = Coin::Dai; let token_3 = Coin::Monero; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + assert_ok!(Dex::create_pool(token_2)); + assert_ok!(Dex::create_pool(token_3)); let ed = get_ed(); let base1 = 10000; @@ -1207,9 +1147,9 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { )); assert_ok!(Dex::add_liquidity( RuntimeOrigin::signed(user), - token_2, + token_1, token_3, - liquidity2, + liquidity1, liquidity3, 1, 1, @@ -1217,8 +1157,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { )); let input_amount = 500; - let expect_out2 = Dex::get_amount_out(&input_amount, &liquidity1, &liquidity2).ok().unwrap(); - let expect_out3 = Dex::get_amount_out(&expect_out2, &liquidity2, &liquidity3).ok().unwrap(); + let expect_out2 = Dex::get_amount_out(&input_amount, &liquidity2, &liquidity1).ok().unwrap(); + let expect_out3 = Dex::get_amount_out(&expect_out2, &liquidity1, &liquidity3).ok().unwrap(); assert_noop!( Dex::swap_exact_tokens_for_tokens( @@ -1234,7 +1174,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_noop!( Dex::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3, token_2], + bvec![token_2, token_1, token_2], input_amount, 80, user, @@ -1244,21 +1184,21 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_ok!(Dex::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3], + bvec![token_2, token_1, token_3], input_amount, // amount_in 80, // amount_out_min user, )); let pool_id1 = (token_1, token_2); - let pool_id2 = (token_2, token_3); + let pool_id2 = (token_1, token_3); let pallet_account1 = Dex::get_pool_account(&pool_id1); let pallet_account2 = Dex::get_pool_account(&pool_id2); - assert_eq!(balance(user, token_1), base1 + ed - input_amount); - assert_eq!(balance(pallet_account1, token_1), liquidity1 + input_amount); - assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_out2); - assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_out2); + assert_eq!(balance(user, token_2), base2 - liquidity2 - input_amount); + assert_eq!(balance(pallet_account1, token_2), liquidity2 + input_amount); + assert_eq!(balance(pallet_account1, token_1), liquidity1 - expect_out2); + assert_eq!(balance(pallet_account2, token_1), liquidity1 + expect_out2); assert_eq!(balance(pallet_account2, token_3), liquidity3 - expect_out3); assert_eq!(balance(user, token_3), 10000 - liquidity3 + expect_out3); }); @@ -1272,8 +1212,8 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { let token_2 = Coin::Bitcoin; let token_3 = Coin::Ether; - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + assert_ok!(Dex::create_pool(token_2)); + assert_ok!(Dex::create_pool(token_3)); let ed = get_ed(); let base1 = 10000; @@ -1298,9 +1238,9 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { )); assert_ok!(Dex::add_liquidity( RuntimeOrigin::signed(user), - token_2, + token_1, token_3, - liquidity2, + liquidity1, liquidity3, 1, 1, @@ -1308,26 +1248,26 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { )); let exchange_out3 = 100; - let expect_in2 = Dex::get_amount_in(&exchange_out3, &liquidity2, &liquidity3).ok().unwrap(); - let expect_in1 = Dex::get_amount_in(&expect_in2, &liquidity1, &liquidity2).ok().unwrap(); + let expect_in2 = Dex::get_amount_in(&exchange_out3, &liquidity1, &liquidity3).ok().unwrap(); + let expect_in1 = Dex::get_amount_in(&expect_in2, &liquidity2, &liquidity1).ok().unwrap(); assert_ok!(Dex::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3], + bvec![token_2, token_1, token_3], exchange_out3, // amount_out 1000, // amount_in_max user, )); let pool_id1 = (token_1, token_2); - let pool_id2 = (token_2, token_3); + let pool_id2 = (token_1, token_3); let pallet_account1 = Dex::get_pool_account(&pool_id1); let pallet_account2 = Dex::get_pool_account(&pool_id2); - assert_eq!(balance(user, token_1), base1 + ed - expect_in1); - assert_eq!(balance(pallet_account1, token_1), liquidity1 + expect_in1); - assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_in2); - assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_in2); + assert_eq!(balance(user, token_2), base2 - liquidity2 - expect_in1); + assert_eq!(balance(pallet_account1, token_1), liquidity1 - expect_in2); + assert_eq!(balance(pallet_account1, token_2), liquidity2 + expect_in1); + assert_eq!(balance(pallet_account2, token_1), liquidity1 + expect_in2); assert_eq!(balance(pallet_account2, token_3), liquidity3 - exchange_out3); assert_eq!(balance(user, token_3), 10000 - liquidity3 + exchange_out3); }); @@ -1439,7 +1379,7 @@ fn cannot_block_pool_creation() { } // User can still create the pool - assert_ok!(Dex::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(Dex::create_pool(token_2)); // User has to transfer one Coin(2) token to the pool account (otherwise add_liquidity will // fail with `CoinTwoDepositDidNotMeetMinimum`), also transfer native token for the same error. diff --git a/substrate/dex/primitives/src/lib.rs b/substrate/dex/primitives/src/lib.rs index 8f30a2539..725e37f07 100644 --- a/substrate/dex/primitives/src/lib.rs +++ b/substrate/dex/primitives/src/lib.rs @@ -28,7 +28,7 @@ use sp_std::vec::Vec; use frame_support::traits::tokens::{Balance, AssetId as CoinId}; -use serai_primitives::{Coin, COINS}; +use serai_primitives::Coin; /// Stores the lp_token coin id a particular pool has been assigned. #[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] @@ -66,20 +66,17 @@ pub enum MultiCoinIdConversionResult { pub trait BenchmarkHelper { /// Returns an `CoinId` from a given integer. fn coin_id(coin_id: u32) -> CoinId; - - /// Returns a `MultiCoinId` from a given integer. - fn multicoin_id(coin_id: u32) -> MultiCoinId; } #[cfg(feature = "runtime-benchmarks")] -impl BenchmarkHelper 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] - } - - fn multicoin_id(coin_id: u32) -> Coin { - COINS[usize::try_from(coin_id).unwrap() % COINS.len()] +mod runtime_benchmarks { + use super::*; + use serai_primitives::COINS; + impl BenchmarkHelper 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] + } } } diff --git a/substrate/in-instructions/pallet/Cargo.toml b/substrate/in-instructions/pallet/Cargo.toml index e74e219eb..767f3e3c3 100644 --- a/substrate/in-instructions/pallet/Cargo.toml +++ b/substrate/in-instructions/pallet/Cargo.toml @@ -17,11 +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-std = { 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,8 +29,8 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur serai-primitives = { path = "../../primitives", default-features = false } in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false } -dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", 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] @@ -40,11 +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-std/std", + "sp-core/std", "frame-system/std", "frame-support/std", @@ -52,8 +52,8 @@ std = [ "serai-primitives/std", "in-instructions-primitives/std", - "dex-pallet/std", "coins-pallet/std", + "dex-pallet/std", "validator-sets-pallet/std", ] default = ["std"] diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index 7cc59441c..30b9dae79 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -23,34 +23,34 @@ 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 sp_std::vec; + + use serai_primitives::{Coin, SubstrateAmount, Amount, Balance}; use frame_support::pallet_prelude::*; use frame_system::{pallet_prelude::*, RawOrigin}; use coins_pallet::{ Config as CoinsConfig, Pallet as Coins, - primitives::{OutInstructionWithBalance, OutInstruction}, + 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, }; - use dex_pallet::{Config as DexConfig, Pallet as Dex}; - - use serai_primitives::{Coin, SubstrateAmount, Amount, Balance}; use super::*; #[pallet::config] pub trait Config: frame_system::Config - + ValidatorSetsConfig + CoinsConfig + DexConfig + + ValidatorSetsConfig { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -98,23 +98,11 @@ pub mod pallet { Coins::::mint(address.into(), instruction.balance)?; } InInstruction::Dex(call) => { - // This will only be initiated by external chain txs. That is why we only need - // adding liquidity and swaps. Other functionalities(create_pool, remove_liq etc.) - // might be called directly from serai as a native operation. - // - // Hence, AddLiquidity call here actually swaps and adds liquidity. - // we will swap half of the given coin for SRI to be able to - // provide symmetric liquidity. So the pool has be be created before - // for this to be successful. - // - // And for swaps, they are done on an internal address like a temp account. - // we mint the deposited coin into that account, do swap on it and burn the - // received coin. This way account will be back on initial balance(since the minted coin - // will be moved to pool account.) and burned coin will be seen by processor and sent - // to given external address. - + // 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::AddLiquidity(address) => { + DexCall::SwapAndAddLiquidity(address) => { let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into()); let coin = instruction.balance.coin; @@ -123,7 +111,7 @@ pub mod pallet { // swap half of it for SRI let half = instruction.balance.amount.0 / 2; - let path = BoundedVec::truncate_from(vec![coin, Coin::Serai]); + let path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap(); Dex::::swap_exact_tokens_for_tokens( origin.clone().into(), path, @@ -198,7 +186,7 @@ pub mod pallet { let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into()); Dex::::swap_exact_tokens_for_tokens( origin.into(), - BoundedVec::truncate_from(path), + BoundedVec::try_from(path).unwrap(), instruction.balance.amount.0, out_balance.amount.0, send_to.into(), @@ -210,10 +198,10 @@ pub mod pallet { // see how much we got let coin_balance = Coins::::balance(IN_INSTRUCTION_EXECUTOR.into(), out_balance.coin); - // TODO: data shouldn't come here from processor just to go back to it. 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 }, diff --git a/substrate/in-instructions/primitives/Cargo.toml b/substrate/in-instructions/primitives/Cargo.toml index 789b1bb5e..6f445ed2e 100644 --- a/substrate/in-instructions/primitives/Cargo.toml +++ b/substrate/in-instructions/primitives/Cargo.toml @@ -18,8 +18,8 @@ serde = { version = "1", default-features = false, features = ["derive", "alloc" scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive"] } -sp-application-crypto = { 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-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } serai-primitives = { path = "../../primitives", default-features = false } @@ -35,6 +35,7 @@ std = [ "scale-info/std", "sp-std/std", + "sp-application-crypto/std", "sp-runtime/std", "serai-primitives/std", diff --git a/substrate/in-instructions/primitives/src/lib.rs b/substrate/in-instructions/primitives/src/lib.rs index beada06ad..56a7ec424 100644 --- a/substrate/in-instructions/primitives/src/lib.rs +++ b/substrate/in-instructions/primitives/src/lib.rs @@ -24,19 +24,8 @@ pub use shorthand::*; pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb -// This is just an account that will make ops in behalf of users for the -// in instructions that is coming in. Not to be confused with in_instructions pallet. -// in_instructions are a pallet(a module) and that is just and account. -pub const IN_INSTRUCTION_EXECUTOR: SeraiAddress = system_address(b"InInstructionExecutor"); - -#[derive( - Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo, -)] -#[cfg_attr(feature = "std", derive(Zeroize))] -pub enum InInstruction { - Transfer(SeraiAddress), - Dex(DexCall), -} +// This is the account which will be the origin for any dispatched `InInstruction`s. +pub const IN_INSTRUCTION_EXECUTOR: SeraiAddress = system_address(b"InInstructions-executor"); #[derive( Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo, @@ -72,12 +61,22 @@ impl OutAddress { )] #[cfg_attr(feature = "std", derive(Zeroize))] pub enum DexCall { - // address to sent the lp tokens to - AddLiquidity(SeraiAddress), - // out balance and out address + // address to send the lp tokens to + // TODO: Update this per documentation/Shorthand + SwapAndAddLiquidity(SeraiAddress), + // minimum out balance and out address Swap(Balance, OutAddress), } +#[derive( + Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo, +)] +#[cfg_attr(feature = "std", derive(Zeroize))] +pub enum InInstruction { + Transfer(SeraiAddress), + Dex(DexCall), +} + #[derive( Clone, PartialEq, diff --git a/substrate/in-instructions/primitives/src/shorthand.rs b/substrate/in-instructions/primitives/src/shorthand.rs index 8f415add5..e6a8de512 100644 --- a/substrate/in-instructions/primitives/src/shorthand.rs +++ b/substrate/in-instructions/primitives/src/shorthand.rs @@ -26,7 +26,7 @@ pub enum Shorthand { minimum: Amount, out: OutInstruction, }, - AddLiquidity { + SwapAndAddLiquidity { origin: Option, minimum: Amount, gas: Amount, @@ -47,7 +47,7 @@ impl TryFrom for RefundableInInstruction { Ok(match shorthand { Shorthand::Raw(instruction) => instruction, Shorthand::Swap { .. } => todo!(), - Shorthand::AddLiquidity { .. } => todo!(), + Shorthand::SwapAndAddLiquidity { .. } => todo!(), }) } } diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 6941296f1..e3dd46699 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -6,8 +6,8 @@ use sc_service::ChainType; use serai_runtime::{ primitives::*, WASM_BINARY, opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, - SystemConfig, ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig, - AuthorityDiscoveryConfig, CoinsConfig, + SystemConfig, CoinsConfig, DexConfig, ValidatorSetsConfig, SessionConfig, BabeConfig, + GrandpaConfig, AuthorityDiscoveryConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -43,6 +43,8 @@ fn testnet_genesis( .collect(), }, + dex: DexConfig { pools: vec![Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero] }, + validator_sets: ValidatorSetsConfig { networks: serai_runtime::primitives::NETWORKS .iter() diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index eb2ca284b..b4c86e847 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -44,12 +44,10 @@ use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, KeyTypeId, traits::{Convert, OpaqueKeys, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Perbill, Permill, + ApplyExtrinsicResult, Perbill, }; -use primitives::{ - PublicKey, SeraiAddress, Coin, AccountLookup, Signature, SubstrateAmount, system_address, -}; +use primitives::{PublicKey, SeraiAddress, Coin, AccountLookup, Signature, SubstrateAmount}; use support::{ traits::{ConstU8, ConstU32, ConstU64, Contains}, @@ -57,7 +55,7 @@ use support::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, IdentityFee, Weight, }, - PalletId, ord_parameter_types, parameter_types, construct_runtime, + parameter_types, construct_runtime, }; use babe::AuthorityId as BabeId; @@ -146,14 +144,6 @@ parameter_types! { ); pub const MaxAuthorities: u32 = validator_sets::primitives::MAX_KEY_SHARES_PER_SET; - - // PalletId has to be exactly 8 byte. Hence It is "DexPalet" instead of "DexPallet". - pub const DexPalletId: PalletId = PalletId(*b"DexPalet"); - pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); -} - -ord_parameter_types! { - pub const CoinConversionOrigin: PublicKey = PublicKey::from(system_address(b"DexPalet")); } pub struct CallFilter; @@ -179,6 +169,7 @@ impl Contains 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::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(_, _)), RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)), @@ -264,7 +255,7 @@ impl dex::Config for Runtime { type Currency = Coins; type Balance = SubstrateAmount; type CoinBalance = SubstrateAmount; - // TODO review if this should be u64/u128 or u64/u256 (and rounding in general). + // TODO: Review if this should be u64/u128 or u64/u256 (and rounding in general). type HigherPrecisionBalance = u128; type CoinId = Coin; @@ -276,16 +267,10 @@ impl dex::Config for Runtime { type PoolCoins = LiquidityTokens; type LPFee = ConstU32<3>; // 0.3% - type PoolSetupFee = ConstU64<0>; // Coin class deposit fees are sufficient to prevent spam - type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type MintMinLiquidity = ConstU64<10000>; type MaxSwapPathLength = ConstU32<3>; // coin1 -> SRI -> coin2 - type PalletId = DexPalletId; - type PoolSetupFeeReceiver = CoinConversionOrigin; - type AllowMultiCoinPools = ConstBool; - type WeightInfo = dex::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs index f15310f83..83947d326 100644 --- a/substrate/validator-sets/pallet/src/lib.rs +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -409,7 +409,7 @@ pub mod pallet { impl Pallet { fn account() -> T::AccountId { - system_address(b"validator-sets").into() + system_address(b"ValidatorSets").into() } // is_bft returns if the network is able to survive any single node becoming byzantine.