Merge branch 'develop' into next

This is an initial resolution of conflicts which does not work.
This commit is contained in:
Luke Parker
2025-01-30 00:56:29 -05:00
128 changed files with 1835 additions and 44261 deletions

View File

@@ -38,7 +38,7 @@ type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
fn create_coin<T: Config>(coin: &ExternalCoin) -> (T::AccountId, AccountIdLookupOf<T>) {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller);
assert_ok!(Coins::<T>::mint(
@@ -47,12 +47,14 @@ fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
));
assert_ok!(Coins::<T>::mint(
caller,
Balance { coin: *coin, amount: Amount(INITIAL_COIN_BALANCE) }
Balance { coin: (*coin).into(), amount: Amount(INITIAL_COIN_BALANCE) }
));
(caller, caller_lookup)
}
fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountIdLookupOf<T>) {
fn create_coin_and_pool<T: Config>(
coin: &ExternalCoin,
) -> (ExternalCoin, T::AccountId, AccountIdLookupOf<T>) {
let (caller, caller_lookup) = create_coin::<T>(coin);
assert_ok!(Dex::<T>::create_pool(*coin));
@@ -62,7 +64,7 @@ fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountI
benchmarks! {
add_liquidity {
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin2 = ExternalCoin::Bitcoin;
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
let add_amount: u64 = 1000;
}: _(
@@ -75,13 +77,13 @@ benchmarks! {
caller
)
verify {
let pool_id = Dex::<T>::get_pool_id(coin1, coin2).unwrap();
let pool_id = Dex::<T>::get_pool_id(coin1, coin2.into()).unwrap();
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
add_amount,
1000u64,
).unwrap();
assert_eq!(
LiquidityTokens::<T>::balance(caller, lp_token).0,
LiquidityTokens::<T>::balance(caller, lp_token.into()).0,
lp_minted
);
assert_eq!(
@@ -91,7 +93,7 @@ benchmarks! {
assert_eq!(
Coins::<T>::balance(
Dex::<T>::get_pool_account(pool_id),
Coin::Bitcoin,
ExternalCoin::Bitcoin.into(),
).0,
1000
);
@@ -99,7 +101,7 @@ benchmarks! {
remove_liquidity {
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = ExternalCoin::Monero;
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
let add_amount: u64 = 100;
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
@@ -117,7 +119,7 @@ benchmarks! {
0u64,
caller,
)?;
let total_supply = LiquidityTokens::<T>::supply(lp_token);
let total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
}: _(
SystemOrigin::Signed(caller),
coin2,
@@ -127,7 +129,7 @@ benchmarks! {
caller
)
verify {
let new_total_supply = LiquidityTokens::<T>::supply(lp_token);
let new_total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
assert_eq!(
new_total_supply,
total_supply - remove_lp_amount
@@ -136,8 +138,8 @@ benchmarks! {
swap_exact_tokens_for_tokens {
let native = Coin::native();
let coin1 = Coin::Bitcoin;
let coin2 = Coin::Ether;
let coin1 = ExternalCoin::Bitcoin;
let coin2 = ExternalCoin::Ether;
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
let (_, _) = create_coin::<T>(&coin2);
@@ -168,21 +170,21 @@ benchmarks! {
caller,
)?;
let path = vec![coin1, native, coin2];
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
let native_balance = Coins::<T>::balance(caller, native).0;
let coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
let coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
}: _(SystemOrigin::Signed(caller), path, swap_amount, 1u64, caller)
verify {
let ed_bump = 2u64;
let new_coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
let new_coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
assert_eq!(new_coin1_balance, coin1_balance - 100u64);
}
swap_tokens_for_exact_tokens {
let native = Coin::native();
let coin1 = Coin::Bitcoin;
let coin2 = Coin::Ether;
let coin1 = ExternalCoin::Bitcoin;
let coin2 = ExternalCoin::Ether;
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
let (_, _) = create_coin::<T>(&coin2);
@@ -208,10 +210,10 @@ benchmarks! {
0u64,
caller,
)?;
let path = vec![coin1, native, coin2];
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
let coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
}: _(
SystemOrigin::Signed(caller),
path.clone(),
@@ -220,7 +222,7 @@ benchmarks! {
caller
)
verify {
let new_coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
let new_coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
assert_eq!(new_coin2_balance, coin2_balance + 100u64);
}

View File

@@ -78,7 +78,7 @@ mod tests;
#[cfg(test)]
mod mock;
use frame_support::ensure;
use frame_support::{ensure, pallet_prelude::*, BoundedBTreeSet};
use frame_system::{
pallet_prelude::{BlockNumberFor, OriginFor},
ensure_signed,
@@ -86,9 +86,12 @@ use frame_system::{
pub use pallet::*;
use sp_runtime::{traits::TrailingZeroInput, DispatchError};
use sp_runtime::{
traits::{TrailingZeroInput, IntegerSquareRoot},
DispatchError,
};
use serai_primitives::{NetworkId, Coin, SubstrateAmount};
use serai_primitives::*;
use sp_std::prelude::*;
pub use types::*;
@@ -103,20 +106,16 @@ pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, BoundedBTreeSet};
use sp_core::sr25519::Public;
use sp_runtime::traits::IntegerSquareRoot;
use coins_pallet::{Pallet as CoinsPallet, Config as CoinsConfig};
use serai_primitives::{Coin, Amount, Balance, SubstrateAmount, reverse_lexicographic_order};
/// Pool ID.
///
/// The pool's `AccountId` is derived from this type. Any changes to the type may necessitate a
/// migration.
pub type PoolId = Coin;
pub type PoolId = ExternalCoin;
/// LiquidityTokens Pallet as an instance of coins pallet.
pub type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
@@ -164,7 +163,7 @@ pub mod pallet {
#[pallet::storage]
#[pallet::getter(fn spot_price_for_block)]
pub type SpotPriceForBlock<T: Config> =
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, Coin, Amount, OptionQuery>;
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, ExternalCoin, Amount, OptionQuery>;
/// Moving window of prices from each block.
///
@@ -173,30 +172,32 @@ pub mod pallet {
/// low to high.
#[pallet::storage]
pub type SpotPrices<T: Config> =
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], u16, OptionQuery>;
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], u16, OptionQuery>;
// SpotPrices, yet with keys stored in reverse lexicographic order.
#[pallet::storage]
pub type ReverseSpotPrices<T: Config> =
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], (), OptionQuery>;
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], (), OptionQuery>;
/// Current length of the `SpotPrices` map.
#[pallet::storage]
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
/// Current position of the median within the `SpotPrices` map;
#[pallet::storage]
pub type CurrentMedianPosition<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
pub type CurrentMedianPosition<T: Config> =
StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
/// Current median price of the prices in the `SpotPrices` map at any given time.
#[pallet::storage]
#[pallet::getter(fn median_price)]
pub type MedianPrice<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
pub type MedianPrice<T: Config> = StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
/// The price used for evaluating economic security, which is the highest observed median price.
#[pallet::storage]
#[pallet::getter(fn security_oracle_value)]
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
pub type SecurityOracleValue<T: Config> =
StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
/// Total swap volume of a given pool in terms of SRI.
#[pallet::storage]
@@ -205,7 +206,7 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
fn restore_median(
coin: Coin,
coin: ExternalCoin,
mut current_median_pos: u16,
mut current_median: Amount,
length: u16,
@@ -256,7 +257,7 @@ pub mod pallet {
MedianPrice::<T>::set(coin, Some(current_median));
}
pub(crate) fn insert_into_median(coin: Coin, amount: Amount) {
pub(crate) fn insert_into_median(coin: ExternalCoin, amount: Amount) {
let new_quantity_of_presences =
SpotPrices::<T>::get(coin, amount.0.to_be_bytes()).unwrap_or(0) + 1;
SpotPrices::<T>::set(coin, amount.0.to_be_bytes(), Some(new_quantity_of_presences));
@@ -286,7 +287,7 @@ pub mod pallet {
Self::restore_median(coin, current_median_pos, current_median, new_length);
}
pub(crate) fn remove_from_median(coin: Coin, amount: Amount) {
pub(crate) fn remove_from_median(coin: ExternalCoin, amount: Amount) {
let mut current_median = MedianPrice::<T>::get(coin).unwrap();
let mut current_median_pos = CurrentMedianPosition::<T>::get(coin).unwrap();
@@ -451,7 +452,7 @@ pub mod pallet {
// insert the new price to our oracle window
// The spot price for 1 coin, in atomic units, to SRI is used
let sri_per_coin =
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::native(), &coin) {
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::Serai, &coin.into()) {
// We use 1 coin to handle rounding errors which may occur with atomic units
// If we used atomic units, any coin whose atomic unit is worth less than SRI's atomic
// unit would cause a 'price' of 0
@@ -493,9 +494,9 @@ pub mod pallet {
/// (the id of which is returned in the `Event::PoolCreated` event).
///
/// Once a pool is created, someone may [`Pallet::add_liquidity`] to it.
pub(crate) fn create_pool(coin: Coin) -> DispatchResult {
pub(crate) fn create_pool(coin: ExternalCoin) -> DispatchResult {
// get pool_id
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
ensure!(!Pools::<T>::contains_key(pool_id), Error::<T>::PoolExists);
let pool_account = Self::get_pool_account(pool_id);
@@ -508,9 +509,11 @@ pub mod pallet {
/// A hook to be called whenever a network's session is rotated.
pub fn on_new_session(network: NetworkId) {
// reset the oracle value
for coin in network.coins() {
SecurityOracleValue::<T>::set(*coin, Self::median_price(coin));
// Only track the price for non-SRI coins as this is SRI denominated
if let NetworkId::External(n) = network {
for coin in n.coins() {
SecurityOracleValue::<T>::set(coin, Self::median_price(coin));
}
}
}
}
@@ -532,7 +535,7 @@ pub mod pallet {
#[allow(clippy::too_many_arguments)]
pub fn add_liquidity(
origin: OriginFor<T>,
coin: Coin,
coin: ExternalCoin,
coin_desired: SubstrateAmount,
sri_desired: SubstrateAmount,
coin_min: SubstrateAmount,
@@ -542,7 +545,7 @@ pub mod pallet {
let sender = ensure_signed(origin)?;
ensure!((sri_desired > 0) && (coin_desired > 0), Error::<T>::WrongDesiredAmount);
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
// create the pool if it doesn't exist. We can just attempt to do that because our checks
// far enough to allow that.
@@ -552,7 +555,7 @@ pub mod pallet {
let pool_account = Self::get_pool_account(pool_id);
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
let coin_reserve = Self::get_balance(&pool_account, coin);
let coin_reserve = Self::get_balance(&pool_account, coin.into());
let sri_amount: SubstrateAmount;
let coin_amount: SubstrateAmount;
@@ -583,16 +586,20 @@ pub mod pallet {
&pool_account,
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
)?;
Self::transfer(&sender, &pool_account, Balance { coin, amount: Amount(coin_amount) })?;
Self::transfer(
&sender,
&pool_account,
Balance { coin: coin.into(), amount: Amount(coin_amount) },
)?;
let total_supply = LiquidityTokens::<T>::supply(coin);
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
let lp_token_amount: SubstrateAmount;
if total_supply == 0 {
lp_token_amount = Self::calc_lp_amount_for_zero_supply(sri_amount, coin_amount)?;
LiquidityTokens::<T>::mint(
pool_account,
Balance { coin, amount: Amount(T::MintMinLiquidity::get()) },
Balance { coin: coin.into(), amount: Amount(T::MintMinLiquidity::get()) },
)?;
} else {
let side1 = Self::mul_div(sri_amount, total_supply, sri_reserve)?;
@@ -605,7 +612,10 @@ pub mod pallet {
Error::<T>::InsufficientLiquidityMinted
);
LiquidityTokens::<T>::mint(mint_to, Balance { coin, amount: Amount(lp_token_amount) })?;
LiquidityTokens::<T>::mint(
mint_to,
Balance { coin: coin.into(), amount: Amount(lp_token_amount) },
)?;
Self::deposit_event(Event::LiquidityAdded {
who: sender,
@@ -626,25 +636,24 @@ pub mod pallet {
#[pallet::weight(T::WeightInfo::remove_liquidity())]
pub fn remove_liquidity(
origin: OriginFor<T>,
coin: Coin,
coin: ExternalCoin,
lp_token_burn: SubstrateAmount,
coin_min_receive: SubstrateAmount,
sri_min_receive: SubstrateAmount,
withdraw_to: T::AccountId,
) -> DispatchResult {
let sender = ensure_signed(origin.clone())?;
ensure!(coin != Coin::Serai, Error::<T>::EqualCoins);
let pool_id = Self::get_pool_id(coin, Coin::Serai).unwrap();
let pool_id = Self::get_pool_id(coin.into(), Coin::native()).unwrap();
ensure!(lp_token_burn > 0, Error::<T>::ZeroLiquidity);
Pools::<T>::get(pool_id).as_ref().ok_or(Error::<T>::PoolNotFound)?;
let pool_account = Self::get_pool_account(pool_id);
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
let coin_reserve = Self::get_balance(&pool_account, coin);
let coin_reserve = Self::get_balance(&pool_account, coin.into());
let total_supply = LiquidityTokens::<T>::supply(coin);
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
let lp_redeem_amount = lp_token_burn;
let sri_amount = Self::mul_div(lp_redeem_amount, sri_reserve, total_supply)?;
@@ -665,14 +674,21 @@ pub mod pallet {
ensure!(coin_reserve_left >= 1, Error::<T>::ReserveLeftLessThanMinimum);
// burn the provided lp token amount that includes the fee
LiquidityTokens::<T>::burn(origin, Balance { coin, amount: Amount(lp_token_burn) })?;
LiquidityTokens::<T>::burn(
origin,
Balance { coin: coin.into(), amount: Amount(lp_token_burn) },
)?;
Self::transfer(
&pool_account,
&withdraw_to,
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
)?;
Self::transfer(&pool_account, &withdraw_to, Balance { coin, amount: Amount(coin_amount) })?;
Self::transfer(
&pool_account,
&withdraw_to,
Balance { coin: coin.into(), amount: Amount(coin_amount) },
)?;
Self::deposit_event(Event::LiquidityRemoved {
who: sender,
@@ -920,11 +936,9 @@ pub mod pallet {
pub fn get_pool_id(coin1: Coin, coin2: Coin) -> Result<PoolId, Error<T>> {
ensure!((coin1 == Coin::Serai) || (coin2 == Coin::Serai), Error::<T>::PoolNotFound);
ensure!(coin1 != coin2, Error::<T>::EqualCoins);
if coin1 == Coin::Serai {
Ok(coin2)
} else {
Ok(coin1)
}
ExternalCoin::try_from(coin1)
.or_else(|()| ExternalCoin::try_from(coin2))
.map_err(|()| Error::<T>::PoolNotFound)
}
/// Returns the balance of each coin in the pool.

View File

@@ -18,7 +18,10 @@
// It has been forked into a crate distributed under the AGPL 3.0.
// Please check the current distribution for up-to-date copyright and licensing information.
use crate::{mock::*, *};
use crate::{
mock::{*, MEDIAN_PRICE_WINDOW_LENGTH},
*,
};
use frame_support::{assert_noop, assert_ok};
pub use coins_pallet as coins;
@@ -72,11 +75,13 @@ fn check_pool_accounts_dont_collide() {
let mut map = HashSet::new();
for coin in coins() {
let account = Dex::get_pool_account(coin);
if map.contains(&account) {
panic!("Collision at {coin:?}");
if let Coin::External(c) = coin {
let account = Dex::get_pool_account(c);
if map.contains(&account) {
panic!("Collision at {c:?}");
}
map.insert(account);
}
map.insert(account);
}
}
@@ -98,11 +103,11 @@ fn can_create_pool() {
let coin_account_deposit: u64 = 0;
let user: PublicKey = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Monero);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_eq!(balance(user, coin1), 1000 - coin_account_deposit);
@@ -111,15 +116,13 @@ fn can_create_pool() {
[Event::<Test>::PoolCreated { pool_id, pool_account: Dex::get_pool_account(pool_id) }]
);
assert_eq!(pools(), vec![pool_id]);
assert_noop!(Dex::create_pool(coin1), Error::<Test>::EqualCoins);
});
}
#[test]
fn create_same_pool_twice_should_fail() {
new_test_ext().execute_with(|| {
let coin = Coin::Dai;
let coin = ExternalCoin::Dai;
assert_ok!(Dex::create_pool(coin));
assert_noop!(Dex::create_pool(coin), Error::<Test>::PoolExists);
});
@@ -129,13 +132,13 @@ fn create_same_pool_twice_should_fail() {
fn different_pools_should_have_different_lp_tokens() {
new_test_ext().execute_with(|| {
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin3 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Bitcoin);
let coin3 = Coin::External(ExternalCoin::Ether);
let pool_id_1_2 = Dex::get_pool_id(coin1, coin2).unwrap();
let pool_id_1_3 = Dex::get_pool_id(coin1, coin3).unwrap();
let lp_token2_1 = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
let lp_token3_1 = coin3;
assert_eq!(
@@ -146,7 +149,7 @@ fn different_pools_should_have_different_lp_tokens() {
}]
);
assert_ok!(Dex::create_pool(coin3));
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
assert_eq!(
events(),
[Event::<Test>::PoolCreated {
@@ -164,13 +167,13 @@ fn can_add_liquidity() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Dai;
let coin3 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Dai);
let coin3 = Coin::External(ExternalCoin::Monero);
let lp_token1 = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
let lp_token2 = coin3;
assert_ok!(Dex::create_pool(coin3));
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(
user,
@@ -179,7 +182,15 @@ fn can_add_liquidity() {
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin3, amount: Amount(1000) }));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
10,
10000,
10,
10000,
user,
));
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
assert!(events().contains(&Event::<Test>::LiquidityAdded {
@@ -198,7 +209,15 @@ fn can_add_liquidity() {
assert_eq!(pool_balance(user, lp_token1), 216);
// try to pass the non-native - native coins, the result should be the same
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin3.try_into().unwrap(),
10,
10000,
10,
10000,
user,
));
let pool_id = Dex::get_pool_id(coin1, coin3).unwrap();
assert!(events().contains(&Event::<Test>::LiquidityAdded {
@@ -223,12 +242,15 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin2 = ExternalCoin::Bitcoin;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(CoinsPallet::<Test>::mint(
user,
Balance { coin: coin2.into(), amount: Amount(1000) }
));
assert_noop!(
Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 1, 1, 1, 1, user),
@@ -242,11 +264,11 @@ fn add_tiny_liquidity_directly_to_pool_address() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin3 = Coin::Dai;
let coin2 = Coin::External(ExternalCoin::Ether);
let coin3 = Coin::External(ExternalCoin::Dai);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin3));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000 * 2) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(10000) }));
@@ -259,7 +281,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
Balance { coin: coin1, amount: Amount(1000) }
));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
10,
10000,
10,
10000,
user,
));
// check the same but for coin3 (non-native token)
let pallet_account = Dex::get_pool_account(Dex::get_pool_id(coin1, coin3).unwrap());
@@ -267,7 +297,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
pallet_account,
Balance { coin: coin2, amount: Amount(1) }
));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin3.try_into().unwrap(),
10,
10000,
10,
10000,
user,
));
});
}
@@ -276,11 +314,11 @@ fn can_remove_liquidity() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Monero);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
let lp_token = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(
user,
@@ -290,7 +328,7 @@ fn can_remove_liquidity() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
100000,
1000000000,
100000,
@@ -302,7 +340,7 @@ fn can_remove_liquidity() {
assert_ok!(Dex::remove_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
total_lp_received,
0,
0,
@@ -334,15 +372,23 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Dai;
let coin2 = Coin::External(ExternalCoin::Dai);
let lp_token = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
10,
10000,
10,
10000,
user,
));
// Only 216 lp_tokens_minted
assert_eq!(pool_balance(user, lp_token), 216);
@@ -350,7 +396,7 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
assert_noop!(
Dex::remove_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
216 + 1, // Try and redeem 10 lp tokens while only 9 minted.
0,
0,
@@ -366,14 +412,22 @@ fn can_quote_price() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Ether);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
200,
10000,
1,
1,
user,
));
assert_eq!(
Dex::quote_price_exact_tokens_for_tokens(Coin::native(), coin2, 3000, false,),
@@ -481,14 +535,22 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() {
let user = system_address(b"user1").into();
let user2 = system_address(b"user2").into();
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin2 = Coin::External(ExternalCoin::Bitcoin);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
200,
10000,
1,
1,
user,
));
let amount = 1;
let quoted_price = 49;
@@ -518,14 +580,22 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() {
let user = system_address(b"user1").into();
let user2 = system_address(b"user2").into();
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Monero);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
200,
10000,
1,
1,
user,
));
let amount = 49;
let quoted_price = 1;
@@ -557,10 +627,10 @@ fn can_swap_with_native() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Ether);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
@@ -570,7 +640,7 @@ fn can_swap_with_native() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -602,8 +672,8 @@ fn can_swap_with_realistic_values() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let sri = Coin::native();
let dai = Coin::Dai;
assert_ok!(Dex::create_pool(dai));
let dai = Coin::External(ExternalCoin::Dai);
assert_ok!(Dex::create_pool(dai.try_into().unwrap()));
const UNIT: u64 = 1_000_000_000;
@@ -620,7 +690,7 @@ fn can_swap_with_realistic_values() {
let liquidity_dai = 1_000_000 * UNIT;
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
dai,
dai.try_into().unwrap(),
liquidity_dai,
liquidity_sri,
1,
@@ -653,9 +723,9 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Monero);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
// Check can't swap an empty pool
assert_noop!(
@@ -676,11 +746,11 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin2 = Coin::External(ExternalCoin::Bitcoin);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
let lp_token = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
@@ -690,7 +760,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -714,7 +784,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
assert_ok!(Dex::remove_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
lp_token_minted,
1,
1,
@@ -787,9 +857,9 @@ fn swap_should_not_work_if_too_much_slippage() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Ether);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
@@ -799,7 +869,7 @@ fn swap_should_not_work_if_too_much_slippage() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -827,10 +897,10 @@ fn can_swap_tokens_for_exact_tokens() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Dai;
let coin2 = Coin::External(ExternalCoin::Dai);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
@@ -844,7 +914,7 @@ fn can_swap_tokens_for_exact_tokens() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -882,11 +952,11 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
let user = system_address(b"user1").into();
let user2 = system_address(b"user2").into();
let coin1 = Coin::native();
let coin2 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Monero);
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
let lp_token = coin2;
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
let base1 = 10000;
let base2 = 1000;
@@ -903,7 +973,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user2),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -947,7 +1017,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
assert_ok!(Dex::remove_liquidity(
RuntimeOrigin::signed(user2),
coin2,
coin2.try_into().unwrap(),
lp_token_minted,
0,
0,
@@ -961,9 +1031,9 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Ether);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
@@ -973,7 +1043,7 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -1001,11 +1071,11 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Dai;
let coin3 = Coin::Monero;
let coin2 = Coin::External(ExternalCoin::Dai);
let coin3 = Coin::External(ExternalCoin::Monero);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin3));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
let base1 = 10000;
let base2 = 10000;
@@ -1019,7 +1089,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -1028,7 +1098,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin3,
coin3.try_into().unwrap(),
liquidity3,
liquidity1,
1,
@@ -1089,11 +1159,11 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::native();
let coin2 = Coin::Bitcoin;
let coin3 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Bitcoin);
let coin3 = Coin::External(ExternalCoin::Ether);
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin3));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
let base1 = 10000;
let base2 = 10000;
@@ -1107,7 +1177,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2,
coin2.try_into().unwrap(),
liquidity2,
liquidity1,
1,
@@ -1116,7 +1186,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin3,
coin3.try_into().unwrap(),
liquidity3,
liquidity1,
1,
@@ -1154,7 +1224,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
fn can_not_swap_same_coin() {
new_test_ext().execute_with(|| {
let user = system_address(b"user1").into();
let coin1 = Coin::Dai;
let coin1 = Coin::External(ExternalCoin::Dai);
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
let exchange_amount = 10;
@@ -1188,10 +1258,10 @@ fn validate_pool_id_sorting() {
// Serai < Bitcoin < Ether < Dai < Monero.
// coin1 <= coin2 for this test to pass.
let native = Coin::native();
let coin1 = Coin::Bitcoin;
let coin2 = Coin::Monero;
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2);
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2);
let coin1 = Coin::External(ExternalCoin::Bitcoin);
let coin2 = Coin::External(ExternalCoin::Monero);
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2.try_into().unwrap());
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2.try_into().unwrap());
assert!(matches!(Dex::get_pool_id(native, native), Err(Error::<Test>::EqualCoins)));
assert!(matches!(Dex::get_pool_id(coin2, coin1), Err(Error::<Test>::PoolNotFound)));
assert!(coin2 > coin1);
@@ -1216,7 +1286,7 @@ fn cannot_block_pool_creation() {
// The target pool the user wants to create is Native <=> Coin(2)
let coin1 = Coin::native();
let coin2 = Coin::Ether;
let coin2 = Coin::External(ExternalCoin::Ether);
// Attacker computes the still non-existing pool account for the target pair
let pool_account = Dex::get_pool_account(Dex::get_pool_id(coin2, coin1).unwrap());
@@ -1238,7 +1308,7 @@ fn cannot_block_pool_creation() {
}
// User can still create the pool
assert_ok!(Dex::create_pool(coin2));
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
// 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.
@@ -1256,7 +1326,15 @@ fn cannot_block_pool_creation() {
));
// add_liquidity shouldn't fail because of the number of consumers
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 100, 9900, 10, 9900, user,));
assert_ok!(Dex::add_liquidity(
RuntimeOrigin::signed(user),
coin2.try_into().unwrap(),
100,
9900,
10,
9900,
user,
));
});
}
@@ -1281,7 +1359,7 @@ fn test_median_price() {
prices.push(OsRng.next_u64());
}
}
let coin = Coin::Bitcoin;
let coin = ExternalCoin::Bitcoin;
assert!(prices.len() >= (2 * usize::from(MEDIAN_PRICE_WINDOW_LENGTH)));
for i in 0 .. prices.len() {