Skip to content

Commit 92daa48

Browse files
authored
Convert rebase token amount when calculate swap amount for StableAsset (#2169)
* swap_with_exact_target call * update * add tests * convert amount when calculate swap amount for StableAsset * update
1 parent 3df918b commit 92daa48

File tree

7 files changed

+552
-578
lines changed

7 files changed

+552
-578
lines changed

modules/aggregated-dex/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ support = { package = "module-support", path = "../support", default-features =
1818
primitives = { package = "acala-primitives", path = "../../primitives", default-features = false }
1919
nutsfinance-stable-asset = { path = "../../ecosystem-modules/stable-asset/lib/stable-asset", version = "0.1.0", default-features = false }
2020
module-dex = { package = "module-dex", path = "../dex", default-features = false }
21+
orml-tokens = { path = "../../orml/tokens", default-features = false }
2122

2223
[dev-dependencies]
23-
orml-tokens = { path = "../../orml/tokens" }
2424
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
2525
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
2626
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
@@ -36,6 +36,7 @@ std = [
3636
"frame-system/std",
3737
"sp-std/std",
3838
"orml-traits/std",
39+
"orml-tokens/std",
3940
"support/std",
4041
"primitives/std",
4142
"nutsfinance-stable-asset/std",

modules/aggregated-dex/src/lib.rs

Lines changed: 165 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use frame_support::{pallet_prelude::*, transactional};
2626
use frame_system::pallet_prelude::*;
2727
use nutsfinance_stable_asset::{traits::StableAsset as StableAssetT, PoolTokenIndex, StableAssetPoolId};
28+
use orml_tokens::ConvertBalance;
2829
use primitives::{Balance, CurrencyId};
2930
#[cfg(feature = "std")]
3031
use serde::{Deserialize, Serialize};
@@ -75,6 +76,8 @@ pub mod module {
7576
#[pallet::constant]
7677
type SwapPathLimit: Get<u32>;
7778

79+
type RebaseTokenAmountConvertor: ConvertBalance<Balance, Balance, AssetId = CurrencyId>;
80+
7881
type WeightInfo: WeightInfo;
7982
}
8083

@@ -124,8 +127,8 @@ pub mod module {
124127
pub fn swap_with_exact_supply(
125128
origin: OriginFor<T>,
126129
paths: Vec<SwapPath>,
127-
supply_amount: Balance,
128-
min_target_amount: Balance,
130+
#[pallet::compact] supply_amount: Balance,
131+
#[pallet::compact] min_target_amount: Balance,
129132
) -> DispatchResult {
130133
let who = ensure_signed(origin)?;
131134
let paths: BoundedVec<SwapPath, T::SwapPathLimit> =
@@ -134,6 +137,26 @@ pub mod module {
134137
Ok(())
135138
}
136139

140+
#[pallet::weight(<T as Config>::WeightInfo::swap_with_exact_supply(
141+
paths.iter().fold(0, |u, swap_path| match swap_path {
142+
SwapPath::Dex(v) => u + (v.len() as u32),
143+
SwapPath::Taiga(_, _, _) => u + 1
144+
})
145+
))]
146+
#[transactional]
147+
pub fn swap_with_exact_target(
148+
origin: OriginFor<T>,
149+
paths: Vec<SwapPath>,
150+
#[pallet::compact] target_amount: Balance,
151+
#[pallet::compact] max_supply_amount: Balance,
152+
) -> DispatchResult {
153+
let who = ensure_signed(origin)?;
154+
let paths: BoundedVec<SwapPath, T::SwapPathLimit> =
155+
paths.try_into().map_err(|_| Error::<T>::InvalidSwapPath)?;
156+
let _ = Self::do_aggregated_swap(&who, &paths, SwapLimit::ExactTarget(max_supply_amount, target_amount))?;
157+
Ok(())
158+
}
159+
137160
/// Update the aggregated swap paths for AggregatedSwap to swap TokenA to TokenB.
138161
///
139162
/// Requires `GovernanceOrigin`
@@ -169,6 +192,110 @@ pub mod module {
169192
}
170193

171194
impl<T: Config> Pallet<T> {
195+
fn taiga_get_best_route(
196+
supply_currency_id: CurrencyId,
197+
target_currency_id: CurrencyId,
198+
supply_amount: Balance,
199+
) -> Option<(StableAssetPoolId, PoolTokenIndex, PoolTokenIndex, Balance)> {
200+
T::StableAsset::get_best_route(
201+
supply_currency_id,
202+
target_currency_id,
203+
T::RebaseTokenAmountConvertor::convert_balance(supply_amount, supply_currency_id),
204+
)
205+
.map(|(pool_id, input_index, output_index, output_amount)| {
206+
(
207+
pool_id,
208+
input_index,
209+
output_index,
210+
T::RebaseTokenAmountConvertor::convert_balance_back(output_amount, target_currency_id),
211+
)
212+
})
213+
}
214+
215+
fn taiga_get_swap_input_amount(
216+
pool_id: StableAssetPoolId,
217+
input_asset_index: PoolTokenIndex,
218+
output_asset_index: PoolTokenIndex,
219+
output_amount: Balance,
220+
) -> Option<(Balance, Balance)> {
221+
let pool_info = T::StableAsset::pool(pool_id)?;
222+
let input_currency_id = pool_info.assets.get(input_asset_index as usize)?;
223+
let output_currency_id = pool_info.assets.get(output_asset_index as usize)?;
224+
225+
T::StableAsset::get_swap_input_amount(
226+
pool_id,
227+
input_asset_index,
228+
output_asset_index,
229+
T::RebaseTokenAmountConvertor::convert_balance(output_amount, *output_currency_id),
230+
)
231+
.map(|swap_result| {
232+
(
233+
T::RebaseTokenAmountConvertor::convert_balance_back(swap_result.dx, *input_currency_id),
234+
T::RebaseTokenAmountConvertor::convert_balance_back(swap_result.dy, *output_currency_id),
235+
)
236+
})
237+
}
238+
239+
fn taiga_get_swap_output_amount(
240+
pool_id: StableAssetPoolId,
241+
input_asset_index: PoolTokenIndex,
242+
output_asset_index: PoolTokenIndex,
243+
input_amount: Balance,
244+
) -> Option<(Balance, Balance)> {
245+
let pool_info = T::StableAsset::pool(pool_id)?;
246+
let input_currency_id = pool_info.assets.get(input_asset_index as usize)?;
247+
let output_currency_id = pool_info.assets.get(output_asset_index as usize)?;
248+
249+
T::StableAsset::get_swap_output_amount(
250+
pool_id,
251+
input_asset_index,
252+
output_asset_index,
253+
T::RebaseTokenAmountConvertor::convert_balance(input_amount, *input_currency_id),
254+
)
255+
.map(|swap_result| {
256+
(
257+
T::RebaseTokenAmountConvertor::convert_balance_back(swap_result.dx, *input_currency_id),
258+
T::RebaseTokenAmountConvertor::convert_balance_back(swap_result.dy, *output_currency_id),
259+
)
260+
})
261+
}
262+
263+
fn taiga_swap(
264+
who: &T::AccountId,
265+
pool_id: StableAssetPoolId,
266+
input_asset_index: PoolTokenIndex,
267+
output_asset_index: PoolTokenIndex,
268+
input_amount: Balance,
269+
min_output_amount: Balance,
270+
) -> sp_std::result::Result<(Balance, Balance), DispatchError> {
271+
let pool_info = T::StableAsset::pool(pool_id).ok_or(Error::<T>::InvalidPoolId)?;
272+
let asset_length = pool_info.assets.len() as u32;
273+
let input_currency_id = pool_info
274+
.assets
275+
.get(input_asset_index as usize)
276+
.ok_or(Error::<T>::InvalidTokenIndex)?;
277+
let output_currency_id = pool_info
278+
.assets
279+
.get(output_asset_index as usize)
280+
.ok_or(Error::<T>::InvalidTokenIndex)?;
281+
282+
T::StableAsset::swap(
283+
who,
284+
pool_id,
285+
input_asset_index,
286+
output_asset_index,
287+
T::RebaseTokenAmountConvertor::convert_balance(input_amount, *input_currency_id),
288+
T::RebaseTokenAmountConvertor::convert_balance(min_output_amount, *output_currency_id),
289+
asset_length,
290+
)
291+
.map(|(dx, dy)| {
292+
(
293+
T::RebaseTokenAmountConvertor::convert_balance_back(dx, *input_currency_id),
294+
T::RebaseTokenAmountConvertor::convert_balance_back(dy, *output_currency_id),
295+
)
296+
})
297+
}
298+
172299
fn check_swap_paths(paths: &[SwapPath]) -> sp_std::result::Result<(CurrencyId, CurrencyId), DispatchError> {
173300
ensure!(!paths.is_empty(), Error::<T>::InvalidSwapPath);
174301
let mut supply_currency_id: Option<CurrencyId> = None;
@@ -247,13 +374,14 @@ impl<T: Config> Pallet<T> {
247374
}
248375
SwapPath::Taiga(pool_id, supply_asset_index, target_asset_index) => {
249376
// use the output of the previous swap as input.
250-
output_amount = T::StableAsset::get_swap_output_amount(
377+
let (_, actual_output_amount) = Self::taiga_get_swap_output_amount(
251378
*pool_id,
252379
*supply_asset_index,
253380
*target_asset_index,
254381
output_amount,
255-
)
256-
.map(|swap_result| swap_result.dy)?;
382+
)?;
383+
384+
output_amount = actual_output_amount;
257385
}
258386
}
259387
}
@@ -277,13 +405,15 @@ impl<T: Config> Pallet<T> {
277405
input_amount = supply_amount;
278406
}
279407
SwapPath::Taiga(pool_id, supply_asset_index, target_asset_index) => {
280-
input_amount = T::StableAsset::get_swap_input_amount(
408+
// calculate the input amount
409+
let (actual_input_amount, _) = Self::taiga_get_swap_input_amount(
281410
*pool_id,
282411
*supply_asset_index,
283412
*target_asset_index,
284413
input_amount,
285-
)
286-
.map(|swap_result| swap_result.dx)?;
414+
)?;
415+
416+
input_amount = actual_input_amount;
287417
}
288418
}
289419
}
@@ -328,18 +458,14 @@ impl<T: Config> Pallet<T> {
328458
output_amount = actual_target;
329459
}
330460
SwapPath::Taiga(pool_id, supply_asset_index, target_asset_index) => {
331-
let pool_info = T::StableAsset::pool(*pool_id).ok_or(Error::<T>::InvalidPoolId)?;
332-
let asset_length = pool_info.assets.len() as u32;
333-
334461
// use the output of the previous swap as input.
335-
let (_, actual_target) = T::StableAsset::swap(
462+
let (_, actual_target) = Self::taiga_swap(
336463
who,
337464
*pool_id,
338465
*supply_asset_index,
339466
*target_asset_index,
340467
output_amount,
341468
Zero::zero(),
342-
asset_length,
343469
)?;
344470

345471
output_amount = actual_target;
@@ -353,43 +479,12 @@ impl<T: Config> Pallet<T> {
353479
Ok((exact_supply_amount, output_amount))
354480
}
355481
// Calculate the supply amount first, then execute swap with ExactSupply
356-
SwapLimit::ExactTarget(max_supply_amount, exact_target_amount) => {
357-
let mut input_amount: Balance = exact_target_amount;
358-
359-
for path in paths.iter().rev() {
360-
match path {
361-
SwapPath::Dex(dex_path) => {
362-
// calculate the supply amount
363-
let (supply_amount, _) = T::DEX::get_swap_amount(
364-
dex_path,
365-
SwapLimit::ExactTarget(Balance::max_value(), input_amount),
366-
)
367-
.ok_or(Error::<T>::CannotSwap)?;
368-
369-
input_amount = supply_amount;
370-
}
371-
SwapPath::Taiga(pool_id, supply_asset_index, target_asset_index) => {
372-
let swap_result = T::StableAsset::get_swap_input_amount(
373-
*pool_id,
374-
*supply_asset_index,
375-
*target_asset_index,
376-
input_amount,
377-
)
378-
.ok_or(Error::<T>::CannotSwap)?;
379-
380-
input_amount = swap_result.dx;
381-
}
382-
}
383-
}
384-
385-
// the result must meet the SwapLimit.
386-
ensure!(
387-
!input_amount.is_zero() && input_amount <= max_supply_amount,
388-
Error::<T>::CannotSwap
389-
);
482+
SwapLimit::ExactTarget(_max_supply_amount, exact_target_amount) => {
483+
let (supply_amount, _) =
484+
Self::get_aggregated_swap_amount(paths, swap_limit).ok_or(Error::<T>::CannotSwap)?;
390485

391486
// actually swap by `ExactSupply` limit
392-
Self::do_aggregated_swap(who, paths, SwapLimit::ExactSupply(input_amount, exact_target_amount))
487+
Self::do_aggregated_swap(who, paths, SwapLimit::ExactSupply(supply_amount, exact_target_amount))
393488
}
394489
}
395490
}
@@ -434,6 +529,10 @@ impl<T: Config> Swap<T::AccountId, Balance, CurrencyId> for DexSwap<T> {
434529
/// Swap by Taiga pool.
435530
pub struct TaigaSwap<T>(PhantomData<T>);
436531
impl<T: Config> Swap<T::AccountId, Balance, CurrencyId> for TaigaSwap<T> {
532+
// !!! Note: if ths limit is `ExactTarget` and the `max_supply_amount` will cause overflow in
533+
// StableAsset, will return `None`. Because the `get_best_route` of StableAsset treats it as the
534+
// actual input amount. However, it will fail so will not cause loss. Maybe need to modiry
535+
// StableAsset impl to avoid this risk.
437536
fn get_swap_amount(
438537
supply_currency_id: CurrencyId,
439538
target_currency_id: CurrencyId,
@@ -442,25 +541,30 @@ impl<T: Config> Swap<T::AccountId, Balance, CurrencyId> for TaigaSwap<T> {
442541
match limit {
443542
SwapLimit::ExactSupply(supply_amount, min_target_amount) => {
444543
let (pool_id, input_index, output_index, _) =
445-
T::StableAsset::get_best_route(supply_currency_id, target_currency_id, supply_amount)?;
544+
Pallet::<T>::taiga_get_best_route(supply_currency_id, target_currency_id, supply_amount)?;
446545

447-
if let Some(swap_result) =
448-
T::StableAsset::get_swap_output_amount(pool_id, input_index, output_index, supply_amount)
546+
if let Some((input_amount, output_amount)) =
547+
Pallet::<T>::taiga_get_swap_output_amount(pool_id, input_index, output_index, supply_amount)
449548
{
450-
if swap_result.dy >= min_target_amount {
451-
return Some((swap_result.dx, swap_result.dy));
549+
if output_amount >= min_target_amount {
550+
return Some((input_amount, output_amount));
452551
}
453552
}
454553
}
455554
SwapLimit::ExactTarget(max_supply_amount, target_amount) => {
456555
let (pool_id, input_index, output_index, _) =
457-
T::StableAsset::get_best_route(supply_currency_id, target_currency_id, max_supply_amount)?;
556+
Pallet::<T>::taiga_get_best_route(supply_currency_id, target_currency_id, max_supply_amount)?;
458557

459-
if let Some(swap_result) =
460-
T::StableAsset::get_swap_input_amount(pool_id, input_index, output_index, target_amount)
558+
if let Some((input_amount, _)) =
559+
Pallet::<T>::taiga_get_swap_input_amount(pool_id, input_index, output_index, target_amount)
461560
{
462-
if !swap_result.dx.is_zero() && swap_result.dx <= max_supply_amount {
463-
return Some((swap_result.dx, swap_result.dy));
561+
if !input_amount.is_zero() && input_amount <= max_supply_amount {
562+
// actually swap by `ExactSupply` limit
563+
return Self::get_swap_amount(
564+
supply_currency_id,
565+
target_currency_id,
566+
SwapLimit::ExactSupply(input_amount, target_amount),
567+
);
464568
}
465569
}
466570
}
@@ -486,22 +590,18 @@ impl<T: Config> Swap<T::AccountId, Balance, CurrencyId> for TaigaSwap<T> {
486590
};
487591

488592
let (pool_id, input_index, output_index, _) =
489-
T::StableAsset::get_best_route(supply_currency_id, target_currency_id, min_target_amount)
593+
Pallet::<T>::taiga_get_best_route(supply_currency_id, target_currency_id, supply_amount)
490594
.ok_or(Error::<T>::CannotSwap)?;
491-
let pool_info = T::StableAsset::pool(pool_id).ok_or(Error::<T>::InvalidPoolId)?;
492-
let asset_length = pool_info.assets.len() as u32;
493-
494-
let (actual_supply, actual_target) = T::StableAsset::swap(
595+
let (actual_supply, actual_target) = Pallet::<T>::taiga_swap(
495596
who,
496597
pool_id,
497598
input_index,
498599
output_index,
499600
supply_amount,
500601
min_target_amount,
501-
asset_length,
502602
)?;
503-
ensure!(actual_target >= min_target_amount, Error::<T>::CannotSwap);
504603

604+
ensure!(actual_target >= min_target_amount, Error::<T>::CannotSwap);
505605
Ok((actual_supply, actual_target))
506606
}
507607
}

0 commit comments

Comments
 (0)