From 29548eeea3b31e146315780530678311bcaf3bdf Mon Sep 17 00:00:00 2001 From: Gregory Hill Date: Fri, 24 Feb 2023 13:30:10 +0000 Subject: [PATCH] feat: best trade swap router rpc Signed-off-by: Gregory Hill --- Cargo.lock | 35 ++ crates/dex-general/src/swap/mod.rs | 11 + crates/dex-general/src/traits.rs | 2 + crates/dex-stable/src/lib.rs | 3 +- crates/dex-stable/src/meta_pool_tests.rs | 76 +++ crates/dex-stable/src/primitives.rs | 8 +- crates/dex-stable/src/rpc.rs | 13 +- crates/dex-stable/src/traits.rs | 207 +++++++- crates/dex-swap-router/rpc/Cargo.toml | 19 + .../rpc/runtime-api/Cargo.toml | 21 + .../rpc/runtime-api/src/lib.rs | 28 ++ crates/dex-swap-router/rpc/src/lib.rs | 116 +++++ crates/dex-swap-router/src/lib.rs | 31 +- crates/dex-swap-router/src/mock.rs | 21 - crates/dex-swap-router/src/rpc.rs | 442 ++++++++++++++++++ crates/dex-swap-router/src/test.rs | 29 +- parachain/Cargo.toml | 1 + parachain/runtime/interlay/Cargo.toml | 4 + parachain/runtime/interlay/src/lib.rs | 10 + parachain/runtime/kintsugi/Cargo.toml | 4 + parachain/runtime/kintsugi/src/lib.rs | 10 + parachain/runtime/testnet-interlay/Cargo.toml | 2 + parachain/runtime/testnet-interlay/src/lib.rs | 14 + parachain/runtime/testnet-kintsugi/Cargo.toml | 2 + parachain/runtime/testnet-kintsugi/src/lib.rs | 14 + parachain/src/service.rs | 4 +- rpc/Cargo.toml | 1 + rpc/src/lib.rs | 6 +- standalone/runtime/Cargo.toml | 4 + standalone/runtime/src/lib.rs | 10 + 30 files changed, 1079 insertions(+), 69 deletions(-) create mode 100644 crates/dex-swap-router/rpc/Cargo.toml create mode 100644 crates/dex-swap-router/rpc/runtime-api/Cargo.toml create mode 100644 crates/dex-swap-router/rpc/runtime-api/src/lib.rs create mode 100644 crates/dex-swap-router/rpc/src/lib.rs create mode 100644 crates/dex-swap-router/src/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index 59d82e9b40..494563ff03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2362,6 +2362,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "dex-swap-router-rpc" +version = "0.1.0" +dependencies = [ + "dex-swap-router", + "dex-swap-router-rpc-runtime-api", + "jsonrpsee", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-rpc", + "sp-runtime", +] + +[[package]] +name = "dex-swap-router-rpc-runtime-api" +version = "0.1.0" +dependencies = [ + "dex-swap-router", + "parity-scale-codec", + "sp-api", + "sp-std", +] + [[package]] name = "diff" version = "0.1.13" @@ -3986,6 +4011,7 @@ dependencies = [ "cumulus-test-relay-sproof-builder", "dex-general-rpc-runtime-api", "dex-stable-rpc-runtime-api", + "dex-swap-router-rpc-runtime-api", "escrow-rpc-runtime-api", "frame-benchmarking", "frame-benchmarking-cli", @@ -4074,6 +4100,7 @@ dependencies = [ "btc-relay-rpc", "dex-general-rpc", "dex-stable-rpc", + "dex-swap-router-rpc", "escrow-rpc", "futures", "interbtc-primitives", @@ -4113,6 +4140,8 @@ dependencies = [ "dex-general", "dex-general-rpc-runtime-api", "dex-stable-rpc-runtime-api", + "dex-swap-router", + "dex-swap-router-rpc-runtime-api", "env_logger 0.9.3", "escrow", "escrow-rpc-runtime-api", @@ -4284,6 +4313,8 @@ dependencies = [ "dex-general", "dex-general-rpc-runtime-api", "dex-stable-rpc-runtime-api", + "dex-swap-router", + "dex-swap-router-rpc-runtime-api", "escrow", "escrow-rpc-runtime-api", "fee", @@ -4676,6 +4707,8 @@ dependencies = [ "dex-general", "dex-general-rpc-runtime-api", "dex-stable-rpc-runtime-api", + "dex-swap-router", + "dex-swap-router-rpc-runtime-api", "escrow", "escrow-rpc-runtime-api", "fee", @@ -12962,6 +12995,7 @@ dependencies = [ "dex-stable", "dex-stable-rpc-runtime-api", "dex-swap-router", + "dex-swap-router-rpc-runtime-api", "escrow", "escrow-rpc-runtime-api", "farming", @@ -13074,6 +13108,7 @@ dependencies = [ "dex-stable", "dex-stable-rpc-runtime-api", "dex-swap-router", + "dex-swap-router-rpc-runtime-api", "escrow", "escrow-rpc-runtime-api", "farming", diff --git a/crates/dex-general/src/swap/mod.rs b/crates/dex-general/src/swap/mod.rs index 9d5e5593dc..ed2421d792 100644 --- a/crates/dex-general/src/swap/mod.rs +++ b/crates/dex-general/src/swap/mod.rs @@ -976,6 +976,13 @@ impl Pallet { } impl ExportDexGeneral for Pallet { + fn get_all_trading_pairs() -> Vec<(T::AssetId, T::AssetId)> { + PairStatuses::::iter() + .filter(|(_, status)| matches!(status, PairStatus::Trading(_))) + .map(|(pair, _)| pair) + .collect() + } + fn get_amount_in_by_path( amount_out: AssetBalance, path: &[T::AssetId], @@ -1052,6 +1059,10 @@ impl ExportDexGeneral for Pallet { } impl ExportDexGeneral for () { + fn get_all_trading_pairs() -> Vec<(AssetId, AssetId)> { + unimplemented!() + } + fn get_amount_in_by_path(_amount_out: AssetBalance, _path: &[AssetId]) -> Result, DispatchError> { unimplemented!() } diff --git a/crates/dex-general/src/traits.rs b/crates/dex-general/src/traits.rs index b39685c36a..f3ddb2f43c 100644 --- a/crates/dex-general/src/traits.rs +++ b/crates/dex-general/src/traits.rs @@ -8,6 +8,8 @@ pub trait GenerateLpAssetId { } pub trait ExportDexGeneral { + fn get_all_trading_pairs() -> Vec<(AssetId, AssetId)>; + fn get_amount_in_by_path(amount_out: AssetBalance, path: &[AssetId]) -> Result, DispatchError>; fn get_amount_out_by_path(amount_in: AssetBalance, path: &[AssetId]) -> Result, DispatchError>; diff --git a/crates/dex-stable/src/lib.rs b/crates/dex-stable/src/lib.rs index 505edf2b03..182e1c0f1d 100644 --- a/crates/dex-stable/src/lib.rs +++ b/crates/dex-stable/src/lib.rs @@ -1410,7 +1410,7 @@ impl Pallet { ) -> Result { let base_pool_currency = Self::get_lp_currency(base_pool_id).ok_or(Error::::InvalidPoolId)?; let base_pool_currency_index = - Self::get_currency_index(pool_id, base_pool_currency).ok_or(Error::::InvalidBasePool)?; + Self::get_currency_index(pool_id, base_pool_currency).ok_or(Error::::InvalidBasePool)? as u32; let mut base_lp_amount = Balance::default(); if base_pool_currency_index != in_index { @@ -1430,6 +1430,7 @@ impl Pallet { Ok(out_amount) } + /// Returns the amount of LP tokens generated by supplying the `amounts` in the pool. pub(crate) fn calculate_currency_amount( pool_id: T::PoolId, amounts: Vec, diff --git a/crates/dex-stable/src/meta_pool_tests.rs b/crates/dex-stable/src/meta_pool_tests.rs index 8c9a9e2c6d..aa066939cb 100644 --- a/crates/dex-stable/src/meta_pool_tests.rs +++ b/crates/dex-stable/src/meta_pool_tests.rs @@ -4348,3 +4348,79 @@ fn meta_pool_swap_underlying_impact_on_base_pool_price_should_work() { ); }) } + +// TODO: rewrite to use mock values +fn calculate_swap(pool_id: PoolId, i: usize, j: usize, in_balance: Balance) -> Balance { + let pool = StableAmm::pools(pool_id).unwrap().get_pool_info(); + let normalized_balances = StableAmm::xp(&pool.balances, &pool.token_multipliers).unwrap(); + let new_in_balance = normalized_balances[i] + in_balance * pool.token_multipliers[i]; + let out_balance = StableAmm::get_y(&pool, i, j, new_in_balance, &normalized_balances).unwrap(); + let out_amount = (normalized_balances[j] - out_balance) / pool.token_multipliers[j]; + let fee = (out_amount * pool.fee) / FEE_DENOMINATOR; + out_amount - fee +} + +#[test] +fn should_calculate_swap_amount_from_base() { + new_test_ext().execute_with(|| { + // asset 0 (base): 1e20 + 1e20 + 1e20 + // asset 1 (base): 1e8 + 1e8 + 1e8 + // asset 2 (base): 1e8 + 1e8 + 1e8 + // asset 0 (meta): 1e18 + // asset 1 (meta): 1e18 + let (base_pool_id, meta_pool_id) = setup_test_meta_pool(); + + fn calculate_token_amount(pool_id: PoolId, amounts: &Vec, deposit: bool) -> Balance { + let pool = StableAmm::pools(pool_id).unwrap().get_pool_info(); + let amp = pool.future_a; + let d0 = StableAmm::xp(&pool.balances, &pool.token_multipliers) + .and_then(|xp| StableAmm::get_d(&xp, amp)) + .unwrap(); + let new_balances = pool + .balances + .iter() + .zip(amounts.iter()) + .map(|(x, y)| if deposit { x + y } else { x - y }) + .collect::>(); + let d1 = StableAmm::xp(&new_balances, &pool.token_multipliers) + .and_then(|xp| StableAmm::get_d(&xp, amp)) + .unwrap(); + let total_supply = ::MultiCurrency::total_issuance(pool.lp_currency_id); + let diff = if deposit { d1 - d0 } else { d0 - d1 }; + (diff * total_supply) / d0 + } + + let swap_amount = 100_000; + let mut base_amounts = vec![0; 3]; + base_amounts[0] = swap_amount; + let base_lp_amount = calculate_token_amount(base_pool_id, &base_amounts, true); + assert_ok!( + StableAmm::stable_amm_calculate_currency_amount(base_pool_id, &base_amounts, true), + base_lp_amount + ); + + assert_eq!( + StableAmm::stable_amm_calculate_swap_amount_from_base(meta_pool_id, base_pool_id, 0, 0, swap_amount) + .unwrap() + .unwrap(), + calculate_swap(meta_pool_id, 0, 1, base_lp_amount), + ); + }) +} + +#[test] +fn should_calculate_swap_amount_to_base() { + new_test_ext().execute_with(|| { + let (base_pool_id, meta_pool_id) = setup_test_meta_pool(); + let base_pool = StableAmm::pools(base_pool_id).unwrap().get_pool_info(); + let token_lp_amount = calculate_swap(meta_pool_id, 0, 1, 100_000); + assert_eq!( + StableAmm::stable_amm_calculate_swap_amount_to_base(meta_pool_id, base_pool_id, 0, 0, 100_000) + .unwrap() + .unwrap(), + StableAmm::calculate_base_remove_liquidity_one_token(&base_pool, token_lp_amount, 0) + .unwrap() + .0, + ); + }) +} diff --git a/crates/dex-stable/src/primitives.rs b/crates/dex-stable/src/primitives.rs index 1173e7e649..3dc2eadfd0 100644 --- a/crates/dex-stable/src/primitives.rs +++ b/crates/dex-stable/src/primitives.rs @@ -73,7 +73,9 @@ pub enum Pool { Meta(MetaPool), } -impl Pool { +impl + Pool +{ pub fn info(self) -> BasePool { match self { Pool::Base(bp) => bp, @@ -88,6 +90,10 @@ impl Pool Option { + self.get_currency_ids().iter().position(|&r| r == currency_id) + } + pub fn get_lp_currency(&self) -> CurrencyId { match self { Pool::Base(bp) => bp.lp_currency_id, diff --git a/crates/dex-stable/src/rpc.rs b/crates/dex-stable/src/rpc.rs index aab9bf6e12..3eb07acce0 100644 --- a/crates/dex-stable/src/rpc.rs +++ b/crates/dex-stable/src/rpc.rs @@ -42,15 +42,12 @@ impl Pallet { Vec::new() } - pub fn get_currency_index(pool_id: T::PoolId, currency_id: T::CurrencyId) -> Option { + pub fn get_currency_index(pool_id: T::PoolId, currency_id: T::CurrencyId) -> Option { if let Some(pool) = Self::pools(pool_id) { - for (i, c) in pool.get_currency_ids().iter().enumerate() { - if *c == currency_id { - return Some(i as u32); - } - } - }; - None + pool.get_currency_index(currency_id) + } else { + None + } } pub fn get_currency(pool_id: T::PoolId, index: u32) -> Option { diff --git a/crates/dex-stable/src/traits.rs b/crates/dex-stable/src/traits.rs index ad8a805f02..0e6a541bd3 100644 --- a/crates/dex-stable/src/traits.rs +++ b/crates/dex-stable/src/traits.rs @@ -12,7 +12,62 @@ pub trait StablePoolLpCurrencyIdGenerate { fn generate_by_pool_id(pool_id: PoolId) -> CurrencyId; } +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] +pub struct StablePair { + pub pool_id: PoolId, + pub base_pool_id: PoolId, + pub token0: CurrencyId, + pub token1: CurrencyId, +} + +impl StablePair +where + CurrencyId: PartialEq, + PoolId: PartialEq, +{ + pub fn path_of(self, token: CurrencyId) -> StablePath { + let swap_mode = if self.pool_id == self.base_pool_id { + StableSwapMode::Single + } else if token == self.token0 { + // input is in base pool + StableSwapMode::FromBase + } else { + // input is in meta pool + StableSwapMode::ToBase + }; + let to_currency = if token == self.token0 { self.token1 } else { self.token0 }; + + StablePath { + pool_id: self.pool_id, + base_pool_id: self.base_pool_id, + mode: swap_mode, + from_currency: token, + to_currency, + } + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct StablePath { + pub pool_id: PoolId, + pub base_pool_id: PoolId, + pub mode: StableSwapMode, + pub from_currency: CurrencyId, + pub to_currency: CurrencyId, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum StableSwapMode { + Single, + FromBase, + ToBase, +} + pub trait StableAmmApi { + fn get_all_trading_pairs() -> Vec>; + fn stable_amm_calculate_currency_amount( pool_id: PoolId, amounts: &[Balance], @@ -21,6 +76,22 @@ pub trait StableAmmApi { fn stable_amm_calculate_swap_amount(pool_id: PoolId, i: usize, j: usize, in_balance: Balance) -> Option; + fn stable_amm_calculate_swap_amount_from_base( + meta_pool_id: PoolId, + base_pool_id: PoolId, + token_index_from: usize, + token_index_to: usize, + amount: Balance, + ) -> Result, DispatchError>; + + fn stable_amm_calculate_swap_amount_to_base( + meta_pool_id: PoolId, + base_pool_id: PoolId, + token_index_from: usize, + token_index_to: usize, + amount: Balance, + ) -> Result, DispatchError>; + fn stable_amm_calculate_remove_liquidity(pool_id: PoolId, amount: Balance) -> Option>; fn stable_amm_calculate_remove_liquidity_one_currency( @@ -29,7 +100,7 @@ pub trait StableAmmApi { index: u32, ) -> Option; - fn currency_index(pool_id: PoolId, currency: CurrencyId) -> Option; + fn currency_index(pool_id: PoolId, currency: CurrencyId) -> Option; fn add_liquidity( who: &AccountId, @@ -41,7 +112,7 @@ pub trait StableAmmApi { fn swap( who: &AccountId, - poo_id: PoolId, + pool_id: PoolId, from_index: u32, to_index: u32, in_amount: Balance, @@ -51,7 +122,7 @@ pub trait StableAmmApi { fn remove_liquidity( who: &AccountId, - poo_id: PoolId, + pool_id: PoolId, lp_amount: Balance, min_amounts: &[Balance], to: &AccountId, @@ -59,7 +130,7 @@ pub trait StableAmmApi { fn remove_liquidity_one_currency( who: &AccountId, - poo_id: PoolId, + pool_id: PoolId, lp_amount: Balance, index: u32, min_amount: Balance, @@ -98,6 +169,53 @@ pub trait StableAmmApi { } impl StableAmmApi for Pallet { + fn get_all_trading_pairs() -> Vec> { + // https://github.com/zenlinkpro/dex-sdk/blob/bba0310df15893913f31c999da9aca71f0bf152c/packages/sdk-router/src/entities/tradeV2.ts#L57 + let mut pairs = vec![]; + let pools: Vec<_> = Pools::::iter().map(|(id, pool)| (id, pool.info())).collect(); + for (base_pool_id, pool) in pools.clone().into_iter() { + // pools linked by the lp token + let related_pools: Vec<_> = pools + .iter() + .filter(|(_, other_pool)| other_pool.currency_ids.contains(&pool.lp_currency_id)) + .cloned() + .collect(); + + for (i, token0) in pool.currency_ids.iter().enumerate() { + for (_j, token1) in pool.currency_ids.iter().enumerate().skip(i + 1) { + pairs.push(StablePair { + pool_id: base_pool_id, + base_pool_id: base_pool_id, + token0: *token0, + token1: *token1, + }); + } + + if related_pools.len() == 0 { + continue; + } + + for (meta_pool_id, other_pool) in &related_pools { + for (_j, token1) in other_pool.currency_ids.iter().enumerate() { + if *token1 == pool.lp_currency_id { + // already added above + continue; + } + + // join meta and base pools + pairs.push(StablePair { + pool_id: *meta_pool_id, + base_pool_id: base_pool_id, + token0: *token0, + token1: *token1, + }); + } + } + } + } + pairs + } + fn stable_amm_calculate_currency_amount( pool_id: T::PoolId, amounts: &[Balance], @@ -135,6 +253,73 @@ impl StableAmmApi fo None } + fn stable_amm_calculate_swap_amount_from_base( + meta_pool_id: T::PoolId, + base_pool_id: T::PoolId, + token_index_from: usize, // base currency + token_index_to: usize, // meta currency + amount: Balance, + ) -> Result, DispatchError> { + if let (Some(meta_pool), Some(base_pool)) = (Self::pools(meta_pool_id), Self::pools(base_pool_id)) { + let base_token = base_pool.get_lp_currency(); + let base_token_index = meta_pool + .get_currency_index(base_token) + .ok_or(Error::::InvalidPooledCurrency)?; + + let mut base_amounts = vec![0; base_pool.get_balances().len()]; + base_amounts[token_index_from] = amount; + + // get LP tokens for supplying `from` currency in base pool + let base_lp_amount = Self::stable_amm_calculate_currency_amount(base_pool_id, &base_amounts, true)?; + if base_token_index == token_index_to { + Ok(Some(base_lp_amount)) + } else { + // swap new LP tokens in meta pool + Ok(Self::stable_amm_calculate_swap_amount( + meta_pool_id, + base_token_index, + token_index_to, + base_lp_amount, + )) + } + } else { + Ok(None) + } + } + + fn stable_amm_calculate_swap_amount_to_base( + meta_pool_id: T::PoolId, + base_pool_id: T::PoolId, + token_index_from: usize, // meta currency + token_index_to: usize, // base currency + amount: Balance, + ) -> Result, DispatchError> { + if let (Some(meta_pool), Some(base_pool)) = (Self::pools(meta_pool_id), Self::pools(base_pool_id)) { + let base_token = base_pool.get_lp_currency(); + let base_token_index = meta_pool + .get_currency_index(base_token) + .ok_or(Error::::InvalidPooledCurrency)?; + + let token_lp_amount = if base_token_index != token_index_from { + // get LP tokens for swapping `from` currency in meta pool + Self::stable_amm_calculate_swap_amount(meta_pool_id, token_index_from, base_token_index, amount) + .ok_or(Error::::Arithmetic)? + } else { + // input is already LP balance + amount + }; + + // burn LP tokens for `to` currency in base pool + Ok(Self::stable_amm_calculate_remove_liquidity_one_currency( + base_pool_id, + token_lp_amount, + token_index_to as u32, + )) + } else { + Ok(None) + } + } + fn stable_amm_calculate_remove_liquidity_one_currency( pool_id: T::PoolId, amount: Balance, @@ -154,7 +339,7 @@ impl StableAmmApi fo None } - fn currency_index(pool_id: T::PoolId, currency: T::CurrencyId) -> Option { + fn currency_index(pool_id: T::PoolId, currency: T::CurrencyId) -> Option { Self::get_currency_index(pool_id, currency) } @@ -170,7 +355,7 @@ impl StableAmmApi fo fn swap( who: &T::AccountId, - poo_id: T::PoolId, + pool_id: T::PoolId, from_index: u32, to_index: u32, in_amount: Balance, @@ -179,7 +364,7 @@ impl StableAmmApi fo ) -> Result { Self::inner_swap( who, - poo_id, + pool_id, from_index as usize, to_index as usize, in_amount, @@ -190,23 +375,23 @@ impl StableAmmApi fo fn remove_liquidity( who: &T::AccountId, - poo_id: T::PoolId, + pool_id: T::PoolId, lp_amount: Balance, min_amounts: &[Balance], to: &T::AccountId, ) -> DispatchResult { - Self::inner_remove_liquidity(poo_id, who, lp_amount, min_amounts, to) + Self::inner_remove_liquidity(pool_id, who, lp_amount, min_amounts, to) } fn remove_liquidity_one_currency( who: &T::AccountId, - poo_id: T::PoolId, + pool_id: T::PoolId, lp_amount: Balance, index: u32, min_amount: Balance, to: &T::AccountId, ) -> Result { - Self::inner_remove_liquidity_one_currency(poo_id, who, lp_amount, index, min_amount, to) + Self::inner_remove_liquidity_one_currency(pool_id, who, lp_amount, index, min_amount, to) } fn remove_liquidity_imbalance( diff --git a/crates/dex-swap-router/rpc/Cargo.toml b/crates/dex-swap-router/rpc/Cargo.toml new file mode 100644 index 0000000000..4eb2374901 --- /dev/null +++ b/crates/dex-swap-router/rpc/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dex-swap-router-rpc" +version = "0.1.0" +authors = ["Interlay Ltd"] +edition = "2021" + +[dependencies] +serde = { version = "1.0.119", features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } + +jsonrpsee = { version = "0.16.2", features = ["server", "macros"] } + +sp-blockchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.31" } +sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.31" } +sp-rpc = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.31" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.31" } + +dex-swap-router = { path = '..' } +dex-swap-router-rpc-runtime-api = { path = "./runtime-api" } diff --git a/crates/dex-swap-router/rpc/runtime-api/Cargo.toml b/crates/dex-swap-router/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000000..d5bf9f9d81 --- /dev/null +++ b/crates/dex-swap-router/rpc/runtime-api/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dex-swap-router-rpc-runtime-api" +version = "0.1.0" +authors = ["Interlay Ltd"] +edition = "2021" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.31" } + +dex-swap-router = { path = '../..', default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", + "sp-std/std", + "dex-swap-router/std", +] diff --git a/crates/dex-swap-router/rpc/runtime-api/src/lib.rs b/crates/dex-swap-router/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000000..26d5e5a4a7 --- /dev/null +++ b/crates/dex-swap-router/rpc/runtime-api/src/lib.rs @@ -0,0 +1,28 @@ +// Copyright 2021-2022 Zenlink. +// Licensed under Apache 2.0. + +//! Runtime API definition for swap router. + +#![cfg_attr(not(feature = "std"), no_std)] +// The `too_many_arguments` warning originates from `decl_runtime_apis` macro. +#![allow(clippy::too_many_arguments)] +// The `unnecessary_mut_passed` warning originates from `decl_runtime_apis` macro. +#![allow(clippy::unnecessary_mut_passed)] +use codec::Codec; +use sp_std::vec::Vec; + +pub use dex_swap_router::Route; + +sp_api::decl_runtime_apis! { + pub trait DexSwapRouterApi where + Balance: Codec, + CurrencyId: Codec, + PoolId: Codec, + { + fn find_best_trade_exact_in( + input_amount: Balance, + input_currency: CurrencyId, + output_currency: CurrencyId + ) -> Option<(Balance, Vec>)>; + } +} diff --git a/crates/dex-swap-router/rpc/src/lib.rs b/crates/dex-swap-router/rpc/src/lib.rs new file mode 100644 index 0000000000..f8c00194d5 --- /dev/null +++ b/crates/dex-swap-router/rpc/src/lib.rs @@ -0,0 +1,116 @@ +//! RPC interface for the swap router pallet. +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] + +use codec::Codec; +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; + +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_rpc::number::NumberOrHex; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, MaybeDisplay}, +}; +use std::sync::Arc; + +pub use dex_swap_router_rpc_runtime_api::{DexSwapRouterApi as DexSwapRouterRuntimeApi, Route}; + +#[rpc(client, server)] +pub trait DexSwapRouterApi { + #[method(name = "dexSwapRouter_findBestTradeExactIn")] + fn find_best_trade_exact_in( + &self, + input_amount: Balance, + input_currency: CurrencyId, + output_currency: CurrencyId, + at: Option, + ) -> RpcResult>)>>; +} + +pub struct DexSwapRouter { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl DexSwapRouter { + pub fn new(client: Arc) -> Self { + Self { + client, + _marker: Default::default(), + } + } +} + +impl DexSwapRouterApiServer<::Hash, Balance, CurrencyId, PoolId> + for DexSwapRouter +where + Block: BlockT, + Balance: Codec + TryInto + std::fmt::Debug + MaybeDisplay + Copy, + CurrencyId: Codec + std::cmp::PartialEq, + PoolId: Codec, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: DexSwapRouterRuntimeApi, +{ + fn find_best_trade_exact_in( + &self, + input_amount: Balance, + input_currency: CurrencyId, + output_currency: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult>)>> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + if let Some((output_amount, route)) = api + .find_best_trade_exact_in(&at, input_amount, input_currency, output_currency) + .map_err(runtime_error_into_rpc_err)? + { + Ok(Some((try_into_rpc_balance(output_amount)?, route))) + } else { + Ok(None) + } + } +} + +fn try_into_rpc_balance + MaybeDisplay + Copy + std::fmt::Debug>( + value: Balance, +) -> RpcResult { + value.try_into().map_err(|_| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "error in swap router pallet", + Some("convert into rpc balance".to_string()), + )) + .into() + }) +} + +/// Error type of this RPC api. +pub enum Error { + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + } + } +} + +fn runtime_error_into_rpc_err(err: impl std::fmt::Display) -> JsonRpseeError { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "error in swap router pallet", + Some(err.to_string()), + )) + .into() +} diff --git a/crates/dex-swap-router/src/lib.rs b/crates/dex-swap-router/src/lib.rs index 067f006406..28e181473b 100644 --- a/crates/dex-swap-router/src/lib.rs +++ b/crates/dex-swap-router/src/lib.rs @@ -6,6 +6,8 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] +pub mod rpc; + #[cfg(test)] mod mock; @@ -18,7 +20,10 @@ pub use weights::WeightInfo; use codec::{Decode, Encode}; -use sp_runtime::traits::{AtLeast32BitUnsigned, One, Zero}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, One, Zero}, + FixedPointOperand, +}; use sp_std::{fmt::Debug, vec::Vec}; use frame_support::{ @@ -28,25 +33,10 @@ use frame_support::{ }; use dex_general::{AssetBalance, ExportDexGeneral}; -use dex_stable::traits::StableAmmApi; - -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] -pub struct StablePath { - pub pool_id: PoolId, - pub base_pool_id: PoolId, - pub mode: StableSwapMode, - pub from_currency: CurrencyId, - pub to_currency: CurrencyId, -} - -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] -pub enum StableSwapMode { - Single, - FromBase, - ToBase, -} +use dex_stable::traits::{StableAmmApi, StablePath, StableSwapMode}; #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum Route { Stable(StablePath), Normal(Vec), @@ -71,6 +61,7 @@ pub mod pallet { type Balance: Parameter + Member + AtLeast32BitUnsigned + + FixedPointOperand + Codec + Default + Copy @@ -208,6 +199,8 @@ impl Pallet { pool_id: T::StablePoolId, currency_id: T::CurrencyId, ) -> Result { - T::StableAMM::currency_index(pool_id, currency_id).ok_or_else(|| Error::::MismatchPoolAndCurrencyId.into()) + T::StableAMM::currency_index(pool_id, currency_id) + .ok_or_else(|| Error::::MismatchPoolAndCurrencyId.into()) + .map(|i| i as u32) } } diff --git a/crates/dex-swap-router/src/mock.rs b/crates/dex-swap-router/src/mock.rs index e3ced42195..1384fb93d3 100644 --- a/crates/dex-swap-router/src/mock.rs +++ b/crates/dex-swap-router/src/mock.rs @@ -85,24 +85,6 @@ impl dex_general::AssetInfo for CurrencyId { } } } - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, MaxEncodedLen, Ord, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum PoolToken { - Token(TokenSymbol), - StablePoolLp(PoolId), -} - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, MaxEncodedLen, Ord, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum PoolType { - P2(PoolToken, PoolToken), - P3(PoolToken, PoolToken, PoolToken), - P4(PoolToken, PoolToken, PoolToken, PoolToken), - P5(PoolToken, PoolToken, PoolToken, PoolToken, PoolToken), - P6(PoolToken, PoolToken, PoolToken, PoolToken, PoolToken, PoolToken), -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; @@ -267,9 +249,6 @@ pub const TOKEN2_UNIT: u128 = 1_000_000_000_000_000_000; pub const TOKEN3_UNIT: u128 = 1_000_000; pub const TOKEN4_UNIT: u128 = 1_000_000; -pub const TOKEN1_ASSET_ID: CurrencyId = CurrencyId::Token(1); -pub const TOKEN2_ASSET_ID: CurrencyId = CurrencyId::Token(2); - pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default() .build_storage::() diff --git a/crates/dex-swap-router/src/rpc.rs b/crates/dex-swap-router/src/rpc.rs new file mode 100644 index 0000000000..369d39ae6a --- /dev/null +++ b/crates/dex-swap-router/src/rpc.rs @@ -0,0 +1,442 @@ +use crate::*; +use dex_stable::traits::{StablePair, StableSwapMode}; +use sp_runtime::{FixedPointNumber, FixedPointOperand, FixedU128}; +use sp_std::vec; + +const MAX_HOPS: u32 = 4; + +#[derive(Clone, Debug, PartialEq)] +pub struct Amount { + value: Balance, + currency: CurrencyId, +} + +impl Amount { + pub fn new(value: Balance, currency: CurrencyId) -> Self { + Self { value, currency } + } +} + +pub trait DexQuery { + type Path; + fn path_of(&self, input: &CurrencyId) -> Self::Path; + fn contains(&self, currency: &CurrencyId) -> bool; + fn get_output_amount(&self, amount_in: &Amount) -> Option>; +} + +#[derive(Clone, Debug)] +pub(crate) enum DexTradingPair { + Stable(StablePair), + Normal(T::CurrencyId, T::CurrencyId), +} + +impl DexQuery for DexTradingPair { + type Path = Route; + + fn path_of(&self, input: &T::CurrencyId) -> Self::Path { + match self { + Self::Stable(stable_pair) => Self::Path::Stable(stable_pair.clone().path_of(input.clone())), + Self::Normal(token0, token1) => Self::Path::Normal(vec![ + input.clone(), + if input == token0 { token1 } else { token0 }.clone(), + ]), + } + } + + fn contains(&self, currency: &T::CurrencyId) -> bool { + match self { + Self::Stable(stable_pair) => &stable_pair.token0 == currency || &stable_pair.token1 == currency, + Self::Normal(token0, token1) => token0 == currency || token1 == currency, + } + } + + fn get_output_amount( + &self, + amount_in: &Amount, + ) -> Option> { + Some(match self { + Self::Stable(stable_pair) => { + let stable_path = stable_pair.clone().path_of(amount_in.currency); + let amount_out = match stable_path.mode { + StableSwapMode::Single => T::StableAMM::stable_amm_calculate_swap_amount( + stable_path.pool_id, + T::StableAMM::currency_index(stable_path.pool_id, stable_path.from_currency)?, + T::StableAMM::currency_index(stable_path.pool_id, stable_path.to_currency)?, + amount_in.value, + )?, + StableSwapMode::FromBase => T::StableAMM::stable_amm_calculate_swap_amount_from_base( + stable_path.pool_id, + stable_path.base_pool_id, + T::StableAMM::currency_index(stable_path.base_pool_id, stable_path.from_currency)?, + T::StableAMM::currency_index(stable_path.pool_id, stable_path.to_currency)?, + amount_in.value, + ) + .ok()??, + StableSwapMode::ToBase => T::StableAMM::stable_amm_calculate_swap_amount_from_base( + stable_path.pool_id, + stable_path.base_pool_id, + T::StableAMM::currency_index(stable_path.pool_id, stable_path.from_currency)?, + T::StableAMM::currency_index(stable_path.base_pool_id, stable_path.to_currency)?, + amount_in.value, + ) + .ok()??, + }; + Amount { + value: amount_out, + currency: stable_path.to_currency, + } + } + Self::Normal(token0, token1) => { + let output_currency = if amount_in.currency == *token0 { token1 } else { token0 }; + let amounts = T::NormalAmm::get_amount_out_by_path( + amount_in.value.into(), + &vec![amount_in.currency, output_currency.clone()], + ) + .ok()?; + let amount_out = T::Balance::from(*amounts.last()?); + Amount { + value: amount_out, + currency: output_currency.clone(), + } + } + }) + } +} + +#[derive(Debug, PartialEq)] +struct Trade { + input_amount: Amount, + output_amount: Amount, + execution_price: FixedU128, + path: Vec, +} + +impl Trade +where + Balance: FixedPointOperand, + CurrencyId: PartialEq, +{ + fn new( + input_amount: Amount, + output_amount: Amount, + path: Vec, + ) -> Self { + let execution_price = + FixedU128::checked_from_rational(output_amount.value, input_amount.value).unwrap_or_default(); + Self { + input_amount, + output_amount, + execution_price, + path, + } + } + + // https://github.com/zenlinkpro/dex-sdk/blob/bba0310df15893913f31c999da9aca71f0bf152c/packages/sdk-router/src/SmartRouterV2.ts#L11 + fn is_better(&self, maybe_other: &Option) -> bool { + if let Some(other) = maybe_other { + if !self.input_amount.currency.eq(&other.input_amount.currency) + || !self.output_amount.currency.eq(&other.output_amount.currency) + { + // TODO: should we return an error here? + false + } else { + // TODO: compare price impact? + self.execution_price.gt(&other.execution_price) + } + } else { + true + } + } +} + +struct TradeFinder { + input_amount: Amount, + output_currency: CurrencyId, + pairs: Vec, +} + +impl TradeFinder +where + Balance: Clone + FixedPointOperand, + CurrencyId: Clone + PartialEq, + TradingPair: Clone + DexQuery, + >::Path: Clone, +{ + pub fn new( + input_amount: Amount, + output_currency: CurrencyId, + pairs: Vec, + ) -> Self { + Self { + input_amount, + output_currency, + pairs, + } + } + + pub fn find_best_trade( + self, + ) -> Option>::Path>> { + // TODO: support routing by exact out + self.find_best_trade_exact_in(self.input_amount.clone(), self.pairs.clone(), vec![], MAX_HOPS) + } + + fn find_best_trade_exact_in( + &self, + input_amount: Amount, + pairs: Vec, + path: Vec<>::Path>, + hop_limit: u32, + ) -> Option>::Path>> { + if hop_limit == 0 { + return None; + } + let mut best_trade = None; + + for (i, pair) in pairs.iter().enumerate() { + if !pair.contains(&input_amount.currency) { + continue; + } + let output_amount = if let Some(output_amount) = pair.get_output_amount(&input_amount) { + output_amount + } else { + continue; + }; + if output_amount.value.is_zero() { + continue; + } + + let current_path = [&path[..], &[pair.path_of(&input_amount.currency)]].concat(); + if self.output_currency == output_amount.currency { + let trade = Trade::new(self.input_amount.clone(), output_amount, current_path); + if trade.is_better(&best_trade) { + best_trade = Some(trade); + } + } else { + // pairs excluding this pair + let mut pairs = self.pairs.clone(); + pairs.remove(i); + + if let Some(trade) = self.find_best_trade_exact_in(output_amount, pairs, current_path, hop_limit - 1) { + if trade.is_better(&best_trade) { + best_trade = Some(trade); + } + } + } + } + + best_trade + } +} + +impl Pallet { + pub(crate) fn get_all_trading_pairs() -> Vec> { + T::StableAMM::get_all_trading_pairs() + .into_iter() + .map(DexTradingPair::Stable) + .chain( + T::NormalAmm::get_all_trading_pairs() + .into_iter() + .map(|(token0, token1)| DexTradingPair::Normal(token0, token1)), + ) + .collect() + } + + pub fn find_best_trade_exact_in( + input_amount: T::Balance, + input_currency: T::CurrencyId, + output_currency: T::CurrencyId, + ) -> Option<(T::Balance, Vec>)> { + let trade = TradeFinder::new( + Amount::new(input_amount, input_currency), + output_currency, + Self::get_all_trading_pairs(), + ) + .find_best_trade()?; + Some((trade.output_amount.value, trade.path)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mock::{CurrencyId, CurrencyId::Token}; + + #[derive(Clone, Debug)] + struct UniswapPair { + token0: CurrencyId, + token1: CurrencyId, + reserve0: u128, + reserve1: u128, + } + + impl UniswapPair { + fn new(token0: CurrencyId, token1: CurrencyId, reserve_in: u128, amount_in: u128, amount_out: u128) -> Self { + Self { + token0, + token1, + reserve0: reserve_in, + // (amountIn * reserveOut) / (reserveIn + amountIn) = amountOut + // (amountIn * reserveOut) = amountOut * (reserveIn + amountIn) + // reserveOut = (amountOut * (reserveIn + amountIn)) / amountIn + reserve1: (amount_out * (reserve_in + amount_in)) / amount_in, + } + } + + fn output_of(&self, currency_id: &CurrencyId) -> CurrencyId { + if currency_id == &self.token0 { + self.token1 + } else { + self.token0 + } + } + + fn reserve_of(&self, currency_id: &CurrencyId) -> u128 { + if currency_id == &self.token0 { + self.reserve0 + } else { + self.reserve1 + } + } + } + + impl DexQuery for UniswapPair { + type Path = (CurrencyId, CurrencyId); + + fn path_of(&self, input: &CurrencyId) -> Self::Path { + (input.clone(), self.output_of(input)) + } + + fn contains(&self, currency: &CurrencyId) -> bool { + &self.token0 == currency || &self.token1 == currency + } + + fn get_output_amount(&self, amount_in: &Amount) -> Option> { + let input_reserve = self.reserve_of(&amount_in.currency); + let output_reserve = self.reserve_of(&self.output_of(&amount_in.currency)); + + let numerator = amount_in.value * output_reserve; + let denominator = input_reserve + amount_in.value; + let amount_out = numerator / denominator; + + if amount_out > output_reserve { + None + } else { + Some(Amount { + value: numerator / denominator, + currency: self.output_of(&amount_in.currency), + }) + } + } + } + + const KBTC: CurrencyId = Token(0); + const USDT: CurrencyId = Token(1); + const KSM: CurrencyId = Token(2); + + #[test] + fn test_set_price() { + assert_eq!( + UniswapPair::new(KBTC, USDT, 100_000_000_000, 100_000_000, 24_804_129_249).get_output_amount(&Amount { + value: 100_000_000, + currency: KBTC, + }), + Some(Amount { + value: 24_804_129_249, + currency: USDT, + }) + ); + } + + #[test] + fn test_find_best_trade() { + // KBTC/USDT = 24732.363767 + let kbtc_usdt = UniswapPair { + token0: KBTC, + token1: USDT, + reserve0: 2_703_163_325, + reserve1: 695_300_254_200, + }; + + // KBTC/KSM = 279.792625403109 + let kbtc_ksm = UniswapPair { + token0: KBTC, + token1: KSM, + reserve0: 2_448_716_486, + reserve1: 7_151_736_602_191_629, + }; + + assert_eq!( + kbtc_ksm.get_output_amount(&Amount { + value: 1000000000000, + currency: KSM, + }), + Some(Amount { + value: 342346, + currency: KBTC, + }) + ); + + // USDT/KSM = 0.027655481836 + let usdt_ksm = UniswapPair { + token0: USDT, + token1: KSM, + reserve0: 50_000_000_000, + reserve1: 1_386_962_552_011_095, + }; + + // KSM/USDT * USDT/KBTC = 36.159196 * 0.00003887 = 0.0014055 + // KSM/KBTC = 0.00342346 + assert_eq!( + TradeFinder::new( + Amount::new(1000000000000, KSM), + KBTC, + vec![kbtc_ksm.clone(), kbtc_usdt.clone(), usdt_ksm] + ) + .find_best_trade() + .unwrap() + .output_amount, + Amount { + value: 342346, + currency: KBTC, + } + ); + + let ksm_usdt = UniswapPair::new(KSM, USDT, 100_000_000_000_000, 1_000_000_000_000, 100_000_000); + + // KSM/USDT * USDT/KBTC = 100 * 0.00003887 = 0.003887 + // KSM/KBTC = 0.00342346 + assert_eq!( + TradeFinder::new( + Amount::new(1000000000000, KSM), + KBTC, + vec![kbtc_ksm, kbtc_usdt, ksm_usdt] + ) + .find_best_trade() + .unwrap() + .output_amount, + Amount { + value: 388720, + currency: KBTC, + } + ); + } + + #[test] + fn test_find_best_trade_insufficient_liquidity() { + assert_eq!( + TradeFinder::new( + Amount::new(100, Token(0)), + Token(1), + vec![UniswapPair { + token0: Token(0), + token1: Token(1), + reserve0: 0, + reserve1: 0, + }] + ) + .find_best_trade(), + None + ); + } +} diff --git a/crates/dex-swap-router/src/test.rs b/crates/dex-swap-router/src/test.rs index 063a54b2fa..00c4ca5906 100644 --- a/crates/dex-swap-router/src/test.rs +++ b/crates/dex-swap-router/src/test.rs @@ -62,14 +62,14 @@ fn setup_stable_pools() { fn setup_pools() { assert_ok!(DexGeneral::create_pair( RawOrigin::Root.into(), - TOKEN1_ASSET_ID, - TOKEN2_ASSET_ID, + Token(TOKEN1_SYMBOL), + Token(TOKEN2_SYMBOL), DEFAULT_FEE_RATE, )); assert_ok!(DexGeneral::add_liquidity( RawOrigin::Signed(USER1).into(), - TOKEN1_ASSET_ID, - TOKEN2_ASSET_ID, + Token(TOKEN1_SYMBOL), + Token(TOKEN2_SYMBOL), 1e18 as Balance, 1e18 as Balance, 0, @@ -85,7 +85,7 @@ fn swap_exact_token_for_tokens_through_stable_pool_with_amount_slippage_should_f setup_pools(); let routes = vec![ - Route::Normal(vec![TOKEN2_ASSET_ID, TOKEN1_ASSET_ID]), + Route::Normal(vec![Token(TOKEN2_SYMBOL), Token(TOKEN1_SYMBOL)]), Route::Stable(StablePath:: { pool_id: 1, base_pool_id: 0, @@ -116,7 +116,7 @@ fn swap_exact_token_for_tokens_through_stable_pool_should_work() { setup_pools(); let routes = vec![ - Route::Normal(vec![TOKEN2_ASSET_ID, TOKEN1_ASSET_ID]), + Route::Normal(vec![Token(TOKEN2_SYMBOL), Token(TOKEN1_SYMBOL)]), Route::Stable(StablePath:: { pool_id: 1, base_pool_id: 0, @@ -157,3 +157,20 @@ fn swap_exact_token_for_tokens_through_stable_pool_should_work() { ); }) } + +#[test] +fn rpc_find_best_trade() { + new_test_ext().execute_with(|| { + setup_stable_pools(); + setup_pools(); + + assert_eq!(RouterPallet::get_all_trading_pairs().len(), 8); + assert_eq!( + RouterPallet::find_best_trade_exact_in(100 as u128, Token(TOKEN1_SYMBOL), Token(TOKEN2_SYMBOL)), + Some(( + 99, + vec![Route::Normal(vec![Token(TOKEN1_SYMBOL), Token(TOKEN2_SYMBOL)])] + )) + ); + }) +} diff --git a/parachain/Cargo.toml b/parachain/Cargo.toml index 77296cbe4f..b587a64500 100644 --- a/parachain/Cargo.toml +++ b/parachain/Cargo.toml @@ -47,6 +47,7 @@ replace-rpc-runtime-api = { path = "../crates/replace/rpc/runtime-api" } loans-rpc-runtime-api = { path = "../crates/loans/rpc/runtime-api" } dex-general-rpc-runtime-api = { path = "../crates/dex-general/rpc/runtime-api" } dex-stable-rpc-runtime-api = { path = "../crates/dex-stable/rpc/runtime-api" } +dex-swap-router-rpc-runtime-api = { path = "../crates/dex-swap-router/rpc/runtime-api" } # Substrate dependencies sc-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" } diff --git a/parachain/runtime/interlay/Cargo.toml b/parachain/runtime/interlay/Cargo.toml index 8e61e2ea37..e6e4867460 100644 --- a/parachain/runtime/interlay/Cargo.toml +++ b/parachain/runtime/interlay/Cargo.toml @@ -99,6 +99,7 @@ clients-info = { path = "../../../crates/clients-info", default-features = false traits = { path = "../../../crates/traits", default-features = false } tx-pause = { path = "../../../crates/tx-pause", default-features = false } dex-general = { path = "../../../crates/dex-general", default-features = false } +dex-swap-router = { path = "../../../crates/dex-swap-router", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -113,6 +114,7 @@ replace-rpc-runtime-api = { path = "../../../crates/replace/rpc/runtime-api", de loans-rpc-runtime-api = { path = "../../../crates/loans/rpc/runtime-api", default-features = false } dex-general-rpc-runtime-api = { path = "../../../crates/dex-general/rpc/runtime-api", default-features = false } dex-stable-rpc-runtime-api = { path = "../../../crates/dex-stable/rpc/runtime-api", default-features = false } +dex-swap-router-rpc-runtime-api = { path = "../../../crates/dex-swap-router/rpc/runtime-api", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false } @@ -219,6 +221,7 @@ std = [ "traits/std", "tx-pause/std", "dex-general/std", + "dex-swap-router/std", "primitives/std", @@ -235,6 +238,7 @@ std = [ "loans-rpc-runtime-api/std", "dex-general-rpc-runtime-api/std", "dex-stable-rpc-runtime-api/std", + "dex-swap-router-rpc-runtime-api/std", "orml-tokens/std", "orml-traits/std", diff --git a/parachain/runtime/interlay/src/lib.rs b/parachain/runtime/interlay/src/lib.rs index 7d3c740047..e03dc43497 100644 --- a/parachain/runtime/interlay/src/lib.rs +++ b/parachain/runtime/interlay/src/lib.rs @@ -1520,6 +1520,16 @@ impl_runtime_apis! { } } + impl dex_swap_router_rpc_runtime_api::DexSwapRouterApi for Runtime { + fn find_best_trade_exact_in( + _input_amount: Balance, + _input_currency: CurrencyId, + _output_currency: CurrencyId + ) -> Option<(Balance, Vec>)> { + Default::default() + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/parachain/runtime/kintsugi/Cargo.toml b/parachain/runtime/kintsugi/Cargo.toml index 69afb31bc3..d0c3f33001 100644 --- a/parachain/runtime/kintsugi/Cargo.toml +++ b/parachain/runtime/kintsugi/Cargo.toml @@ -100,6 +100,7 @@ clients-info = { path = "../../../crates/clients-info", default-features = false traits = { path = "../../../crates/traits", default-features = false } tx-pause = { path = "../../../crates/tx-pause", default-features = false } dex-general = { path = "../../../crates/dex-general", default-features = false } +dex-swap-router = { path = "../../../crates/dex-swap-router", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -114,6 +115,7 @@ replace-rpc-runtime-api = { path = "../../../crates/replace/rpc/runtime-api", de loans-rpc-runtime-api = { path = "../../../crates/loans/rpc/runtime-api", default-features = false } dex-general-rpc-runtime-api = { path = "../../../crates/dex-general/rpc/runtime-api", default-features = false } dex-stable-rpc-runtime-api = { path = "../../../crates/dex-stable/rpc/runtime-api", default-features = false } +dex-swap-router-rpc-runtime-api = { path = "../../../crates/dex-swap-router/rpc/runtime-api", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false } @@ -225,6 +227,7 @@ std = [ "traits/std", "tx-pause/std", "dex-general/std", + "dex-swap-router/std", "primitives/std", @@ -241,6 +244,7 @@ std = [ "loans-rpc-runtime-api/std", "dex-general-rpc-runtime-api/std", "dex-stable-rpc-runtime-api/std", + "dex-swap-router-rpc-runtime-api/std", "orml-tokens/std", "orml-traits/std", diff --git a/parachain/runtime/kintsugi/src/lib.rs b/parachain/runtime/kintsugi/src/lib.rs index b751ea6315..02baefe75a 100644 --- a/parachain/runtime/kintsugi/src/lib.rs +++ b/parachain/runtime/kintsugi/src/lib.rs @@ -1785,6 +1785,16 @@ impl_runtime_apis! { } } + impl dex_swap_router_rpc_runtime_api::DexSwapRouterApi for Runtime { + fn find_best_trade_exact_in( + _input_amount: Balance, + _input_currency: CurrencyId, + _output_currency: CurrencyId + ) -> Option<(Balance, Vec>)> { + Default::default() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/parachain/runtime/testnet-interlay/Cargo.toml b/parachain/runtime/testnet-interlay/Cargo.toml index e6d7ca233d..47e4985e87 100644 --- a/parachain/runtime/testnet-interlay/Cargo.toml +++ b/parachain/runtime/testnet-interlay/Cargo.toml @@ -118,6 +118,7 @@ replace-rpc-runtime-api = { path = "../../../crates/replace/rpc/runtime-api", de loans-rpc-runtime-api = { path = "../../../crates/loans/rpc/runtime-api", default-features = false } dex-general-rpc-runtime-api = { path = "../../../crates/dex-general/rpc/runtime-api", default-features = false } dex-stable-rpc-runtime-api = { path = "../../../crates/dex-stable/rpc/runtime-api", default-features = false } +dex-swap-router-rpc-runtime-api = { path = "../../../crates/dex-swap-router/rpc/runtime-api", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false } @@ -248,6 +249,7 @@ std = [ "loans-rpc-runtime-api/std", "dex-general-rpc-runtime-api/std", "dex-stable-rpc-runtime-api/std", + "dex-swap-router-rpc-runtime-api/std", "orml-tokens/std", "orml-traits/std", diff --git a/parachain/runtime/testnet-interlay/src/lib.rs b/parachain/runtime/testnet-interlay/src/lib.rs index 2c60cd570e..dbf8919ea8 100644 --- a/parachain/runtime/testnet-interlay/src/lib.rs +++ b/parachain/runtime/testnet-interlay/src/lib.rs @@ -1801,6 +1801,20 @@ impl_runtime_apis! { } } + impl dex_swap_router_rpc_runtime_api::DexSwapRouterApi for Runtime { + fn find_best_trade_exact_in( + input_amount: Balance, + input_currency: CurrencyId, + output_currency: CurrencyId + ) -> Option<(Balance, Vec>)> { + DexSwapRouter::find_best_trade_exact_in( + input_amount, + input_currency, + output_currency, + ) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/parachain/runtime/testnet-kintsugi/Cargo.toml b/parachain/runtime/testnet-kintsugi/Cargo.toml index c74f5e437b..67f12aa54e 100644 --- a/parachain/runtime/testnet-kintsugi/Cargo.toml +++ b/parachain/runtime/testnet-kintsugi/Cargo.toml @@ -118,6 +118,7 @@ replace-rpc-runtime-api = { path = "../../../crates/replace/rpc/runtime-api", de loans-rpc-runtime-api = { path = "../../../crates/loans/rpc/runtime-api", default-features = false } dex-general-rpc-runtime-api = { path = "../../../crates/dex-general/rpc/runtime-api", default-features = false } dex-stable-rpc-runtime-api = { path = "../../../crates/dex-stable/rpc/runtime-api", default-features = false } +dex-swap-router-rpc-runtime-api = { path = "../../../crates/dex-swap-router/rpc/runtime-api", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false } @@ -248,6 +249,7 @@ std = [ "loans-rpc-runtime-api/std", "dex-general-rpc-runtime-api/std", "dex-stable-rpc-runtime-api/std", + "dex-swap-router-rpc-runtime-api/std", "orml-tokens/std", "orml-traits/std", diff --git a/parachain/runtime/testnet-kintsugi/src/lib.rs b/parachain/runtime/testnet-kintsugi/src/lib.rs index 098e3c28a8..78c432840b 100644 --- a/parachain/runtime/testnet-kintsugi/src/lib.rs +++ b/parachain/runtime/testnet-kintsugi/src/lib.rs @@ -1812,6 +1812,20 @@ impl_runtime_apis! { } } + impl dex_swap_router_rpc_runtime_api::DexSwapRouterApi for Runtime { + fn find_best_trade_exact_in( + input_amount: Balance, + input_currency: CurrencyId, + output_currency: CurrencyId + ) -> Option<(Balance, Vec>)> { + DexSwapRouter::find_best_trade_exact_in( + input_amount, + input_currency, + output_currency, + ) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/parachain/src/service.rs b/parachain/src/service.rs index 4262eb4256..b1f47ca5ec 100644 --- a/parachain/src/service.rs +++ b/parachain/src/service.rs @@ -104,6 +104,7 @@ pub trait RuntimeApiCollection: > + loans_rpc_runtime_api::LoansApi + dex_general_rpc_runtime_api::DexGeneralApi + dex_stable_rpc_runtime_api::DexStableApi + + dex_swap_router_rpc_runtime_api::DexSwapRouterApi where >::StateBackend: sp_api::StateBackend, { @@ -155,7 +156,8 @@ where UnsignedFixedPoint, > + loans_rpc_runtime_api::LoansApi + dex_general_rpc_runtime_api::DexGeneralApi - + dex_stable_rpc_runtime_api::DexStableApi, + + dex_stable_rpc_runtime_api::DexStableApi + + dex_swap_router_rpc_runtime_api::DexSwapRouterApi, >::StateBackend: sp_api::StateBackend, { } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 85aa0213e9..1922d461b3 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -20,6 +20,7 @@ reward-rpc = { path = "../crates/reward/rpc" } loans-rpc = { path = "../crates/loans/rpc" } dex-general-rpc = { path = "../crates/dex-general/rpc" } dex-stable-rpc = { path = "../crates/dex-stable/rpc" } +dex-swap-router-rpc = { path = "../crates/dex-swap-router/rpc" } vault-registry = { path = "../crates/vault-registry" } primitives = { package = "interbtc-primitives", path = "../primitives" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 5f365a6862..8666d5212e 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -81,12 +81,14 @@ where C::Api: loans_rpc::LoansRuntimeApi, C::Api: dex_general_rpc::DexGeneralRuntimeApi, C::Api: dex_stable_rpc::DexStableRuntimeApi, + C::Api: dex_swap_router_rpc::DexSwapRouterRuntimeApi, C::Api: BlockBuilder, P: TransactionPool + 'static, { use btc_relay_rpc::{BtcRelay, BtcRelayApiServer}; use dex_general_rpc::{DexGeneral, DexGeneralApiServer}; use dex_stable_rpc::{DexStable, DexStableApiServer}; + use dex_swap_router_rpc::{DexSwapRouter, DexSwapRouterApiServer}; use escrow_rpc::{Escrow, EscrowApiServer}; use issue_rpc::{Issue, IssueApiServer}; use loans_rpc::{Loans, LoansApiServer}; @@ -138,7 +140,9 @@ where module.merge(DexGeneral::new(client.clone()).into_rpc())?; - module.merge(DexStable::new(client).into_rpc())?; + module.merge(DexStable::new(client.clone()).into_rpc())?; + + module.merge(DexSwapRouter::new(client).into_rpc())?; Ok(module) } diff --git a/standalone/runtime/Cargo.toml b/standalone/runtime/Cargo.toml index 6477f2e099..98bae3125b 100644 --- a/standalone/runtime/Cargo.toml +++ b/standalone/runtime/Cargo.toml @@ -82,6 +82,7 @@ traits = { path = "../../crates/traits", default-features = false } farming = { path = "../../crates/farming", default-features = false } tx-pause = { path = "../../crates/tx-pause", default-features = false } dex-general = { path = "../../crates/dex-general", default-features = false } +dex-swap-router = { path = "../../crates/dex-swap-router", default-features = false } primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false } @@ -96,6 +97,7 @@ replace-rpc-runtime-api = { path = "../../crates/replace/rpc/runtime-api", defau loans-rpc-runtime-api = { path = "../../crates/loans/rpc/runtime-api", default-features = false } dex-general-rpc-runtime-api = { path = "../../crates/dex-general/rpc/runtime-api", default-features = false } dex-stable-rpc-runtime-api = { path = "../../crates/dex-stable/rpc/runtime-api", default-features = false } +dex-swap-router-rpc-runtime-api = { path = "../../crates/dex-swap-router/rpc/runtime-api", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false } @@ -192,6 +194,7 @@ std = [ "farming/std", "tx-pause/std", "dex-general/std", + "dex-swap-router/std", "primitives/std", @@ -206,6 +209,7 @@ std = [ "loans-rpc-runtime-api/std", "dex-general-rpc-runtime-api/std", "dex-stable-rpc-runtime-api/std", + "dex-swap-router-rpc-runtime-api/std", "orml-asset-registry/std", "orml-tokens/std", diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 9fefbf2166..eb91d6e6a7 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -1735,4 +1735,14 @@ impl_runtime_apis! { Default::default() } } + + impl dex_swap_router_rpc_runtime_api::DexSwapRouterApi for Runtime { + fn find_best_trade_exact_in( + _input_amount: Balance, + _input_currency: CurrencyId, + _output_currency: CurrencyId + ) -> Option<(Balance, Vec>)> { + Default::default() + } + } }