Skip to content

Commit

Permalink
Liquidity pools: Add UTs to for update_token_price() (#1890)
Browse files Browse the repository at this point in the history
* add token price UTs

* update IT

* fix tests compilation
  • Loading branch information
lemunozm authored Jul 9, 2024
1 parent 9a78356 commit e784233
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 128 deletions.
14 changes: 8 additions & 6 deletions libs/mocks/src/pools.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use cfg_traits::{
investments::InvestmentAccountant, PoolInspect, PoolReserve, PriceValue, Seconds,
TrancheTokenPrice,
investments::InvestmentAccountant, PoolInspect, PoolReserve, Seconds, TrancheTokenPrice,
};
use cfg_types::investments::InvestmentInfo;
use frame_support::pallet_prelude::*;
Expand Down Expand Up @@ -86,6 +85,12 @@ pub mod pallet {
register_call!(move |(a, b, c, d)| f(a, b, c, d));
}

pub fn mock_get_price(
f: impl Fn(T::PoolId, T::TrancheId) -> Option<(T::BalanceRatio, Seconds)> + 'static,
) {
register_call!(move |(a, b)| f(a, b));
}

#[allow(non_snake_case)]
pub fn mock_InvestmentAccountant_deposit(
f: impl Fn(&T::AccountId, T::TrancheCurrency, T::Balance) -> DispatchResult + 'static,
Expand Down Expand Up @@ -168,10 +173,7 @@ pub mod pallet {
type PoolId = T::PoolId;
type TrancheId = T::TrancheId;

fn get(
a: T::PoolId,
b: T::TrancheId,
) -> Option<PriceValue<T::CurrencyId, T::BalanceRatio, Seconds>> {
fn get_price(a: T::PoolId, b: T::TrancheId) -> Option<(T::BalanceRatio, Seconds)> {
execute_call!((a, b))
}
}
Expand Down
29 changes: 2 additions & 27 deletions libs/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ pub trait TrancheTokenPrice<AccountId, CurrencyId> {
type BalanceRatio;
type Moment;

fn get(
fn get_price(
pool_id: Self::PoolId,
tranche_id: Self::TrancheId,
) -> Option<PriceValue<CurrencyId, Self::BalanceRatio, Self::Moment>>;
) -> Option<(Self::BalanceRatio, Self::Moment)>;
}

/// Variants for valid Pool updates to send out as events
Expand Down Expand Up @@ -206,31 +206,6 @@ pub trait PoolWriteOffPolicyMutate<PoolId> {
fn worst_case_policy() -> Self::Policy;
}

/// A trait that can be used to retrieve the current price for a currency
pub struct CurrencyPair<CurrencyId> {
pub base: CurrencyId,
pub quote: CurrencyId,
}

pub struct PriceValue<CurrencyId, Rate, Moment> {
pub pair: CurrencyPair<CurrencyId>,
pub price: Rate,
pub last_updated: Moment,
}

pub trait CurrencyPrice<CurrencyId> {
type Rate;
type Moment;

/// Retrieve the latest price of `base` currency, denominated in the `quote`
/// currency If `quote` currency is not passed, then the default `quote`
/// currency is used (when possible)
fn get_latest(
base: CurrencyId,
quote: Option<CurrencyId>,
) -> Option<PriceValue<CurrencyId, Self::Rate, Self::Moment>>;
}

pub trait Permissions<AccountId> {
type Scope;
type Role;
Expand Down
Empty file.
25 changes: 18 additions & 7 deletions pallets/liquidity-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use core::convert::TryFrom;

use cfg_traits::{
liquidity_pools::{InboundQueue, OutboundQueue},
swaps::TokenSwaps,
PreConditions,
};
use cfg_types::{
Expand All @@ -60,7 +61,7 @@ use frame_support::{
use orml_traits::asset_registry::{self, Inspect as _};
pub use pallet::*;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert},
traits::{AtLeast32BitUnsigned, Convert, EnsureMul},
FixedPointNumber, SaturatedConversion,
};
use sp_std::{convert::TryInto, vec};
Expand Down Expand Up @@ -271,6 +272,13 @@ pub mod pallet {
Result = DispatchResult,
>;

/// Type used to retrive market ratio information about currencies
type MarketRatio: TokenSwaps<
Self::AccountId,
CurrencyId = Self::CurrencyId,
Ratio = Self::BalanceRatio,
>;

type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}

Expand Down Expand Up @@ -435,12 +443,15 @@ pub mod pallet {
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

// TODO(future): Once we diverge from 1-to-1 conversions for foreign and pool
// currencies, this price must be first converted into the currency_id and then
// re-denominated to 18 decimals (i.e. `Ratio` precision)
let price_value = T::TrancheTokenPrice::get(pool_id, tranche_id)
let (price, computed_at) = T::TrancheTokenPrice::get_price(pool_id, tranche_id)
.ok_or(Error::<T>::MissingTranchePrice)?;

let foreign_price = T::MarketRatio::market_ratio(
currency_id,
T::PoolInspect::currency_for(pool_id).ok_or(Error::<T>::PoolNotFound)?,
)?
.ensure_mul(price)?;

// Check that the registered asset location matches the destination
match Self::try_get_wrapped_token(&currency_id)? {
LiquidityPoolsWrappedToken::EVM { chain_id, .. } => {
Expand All @@ -459,8 +470,8 @@ pub mod pallet {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
currency,
price: price_value.price.into_inner(),
computed_at: price_value.last_updated,
price: foreign_price.into_inner(),
computed_at,
},
)?;

Expand Down
10 changes: 10 additions & 0 deletions pallets/liquidity-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ frame_support::construct_runtime!(
DomainAddressToAccountId: cfg_mocks::converter::pallet::<Instance1>,
DomainAccountToDomainAddress: cfg_mocks::converter::pallet::<Instance2>,
TransferFilter: cfg_mocks::pre_conditions::pallet,
MarketRatio: cfg_mocks::token_swaps::pallet,
Tokens: orml_tokens,
LiquidityPools: pallet_liquidity_pools,
}
Expand Down Expand Up @@ -91,6 +92,14 @@ impl cfg_mocks::pre_conditions::pallet::Config for Runtime {
type Result = DispatchResult;
}

impl cfg_mocks::token_swaps::pallet::Config for Runtime {
type BalanceIn = Balance;
type BalanceOut = Balance;
type CurrencyId = CurrencyId;
type OrderId = ();
type Ratio = Ratio;
}

parameter_type_with_key! {
pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance {
Default::default()
Expand Down Expand Up @@ -125,6 +134,7 @@ impl pallet_liquidity_pools::Config for Runtime {
type DomainAddressToAccountId = DomainAddressToAccountId;
type ForeignInvestment = ForeignInvestment;
type GeneralCurrencyPrefix = CurrencyPrefix;
type MarketRatio = MarketRatio;
type OutboundQueue = Gateway;
type Permission = Permissions;
type PoolId = PoolId;
Expand Down
140 changes: 134 additions & 6 deletions pallets/liquidity-pools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use cfg_traits::{liquidity_pools::InboundQueue, Millis};
use cfg_types::{
domain_address::DomainAddress,
permissions::{PermissionScope, PoolRole, Role},
tokens::{AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata},
tokens::{AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata, LocalAssetId},
};
use cfg_utils::vec_to_fixed_array;
use frame_support::{
Expand All @@ -13,7 +13,7 @@ use frame_support::{
PalletInfo as _,
},
};
use sp_runtime::{DispatchError, TokenError};
use sp_runtime::{traits::Saturating, DispatchError, TokenError};
use staging_xcm::{
v4::{Junction::*, Location, NetworkId},
VersionedLocation,
Expand All @@ -28,13 +28,16 @@ const CONTRACT_ACCOUNT_ID: AccountId = AccountId::new([1; 32]);
const EVM_ADDRESS: DomainAddress = DomainAddress::EVM(CHAIN_ID, CONTRACT_ACCOUNT);
const AMOUNT: Balance = 100;
const CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1);
const POOL_CURRENCY_ID: CurrencyId = CurrencyId::LocalAsset(LocalAssetId(1));
const POOL_ID: PoolId = 1;
const TRANCHE_ID: TrancheId = [1; 16];
const NOW: Millis = 0;
const NAME: &[u8] = b"Token name";
const SYMBOL: &[u8] = b"Token symbol";
const DECIMALS: u8 = 6;
const TRANCHE_CURRENCY: CurrencyId = CurrencyId::Tranche(POOL_ID, TRANCHE_ID);
const TRANCHE_TOKEN_PRICE: Ratio = Ratio::from_rational(10, 1);
const MARKET_RATIO: Ratio = Ratio::from_rational(2, 1);

mod util {
use super::*;
Expand Down Expand Up @@ -118,7 +121,7 @@ mod transfer {
})
}

mod erroring_out_when {
mod erroring_out {
use super::*;

#[test]
Expand Down Expand Up @@ -326,7 +329,7 @@ mod transfer_tranche_tokens {
})
}

mod erroring_out_when {
mod erroring_out {
use super::*;

#[test]
Expand Down Expand Up @@ -461,7 +464,7 @@ mod add_pool {
})
}

mod erroring_out_when {
mod erroring_out {
use super::*;

#[test]
Expand Down Expand Up @@ -540,7 +543,7 @@ mod add_tranche {
})
}

mod erroring_out_when {
mod erroring_out {
use super::*;

#[test]
Expand Down Expand Up @@ -619,6 +622,131 @@ mod add_tranche {
}
}

mod update_token_price {
use super::*;

#[test]
fn success() {
System::externalities().execute_with(|| {
Pools::mock_get_price(|_, _| Some((TRANCHE_TOKEN_PRICE, 1234)));
Pools::mock_currency_for(|_| Some(POOL_CURRENCY_ID));
MarketRatio::mock_market_ratio(|target, origin| {
assert_eq!(target, CURRENCY_ID);
assert_eq!(origin, POOL_CURRENCY_ID);
Ok(MARKET_RATIO)
});
AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata()));
Gateway::mock_submit(|sender, destination, msg| {
assert_eq!(sender, ALICE);
assert_eq!(destination, EVM_ADDRESS.domain());
assert_eq!(
msg,
Message::UpdateTrancheTokenPrice {
pool_id: POOL_ID,
tranche_id: TRANCHE_ID,
currency: util::currency_index(CURRENCY_ID),
price: TRANCHE_TOKEN_PRICE
.saturating_mul(MARKET_RATIO)
.into_inner(),
computed_at: 1234
}
);
Ok(())
});

assert_ok!(LiquidityPools::update_token_price(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
CURRENCY_ID,
EVM_ADDRESS.domain(),
));
})
}

mod erroring_out {
use super::*;

#[test]
fn with_missing_tranche_price() {
System::externalities().execute_with(|| {
Pools::mock_get_price(|_, _| None);

assert_noop!(
LiquidityPools::update_token_price(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
CURRENCY_ID,
EVM_ADDRESS.domain(),
),
Error::<Runtime>::MissingTranchePrice,
);
})
}

#[test]
fn with_wrong_pool() {
System::externalities().execute_with(|| {
Pools::mock_get_price(|_, _| Some((TRANCHE_TOKEN_PRICE, 1234)));
Pools::mock_currency_for(|_| None);

assert_noop!(
LiquidityPools::update_token_price(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
CURRENCY_ID,
EVM_ADDRESS.domain(),
),
Error::<Runtime>::PoolNotFound,
);
})
}

#[test]
fn with_no_market_ratio() {
System::externalities().execute_with(|| {
Pools::mock_get_price(|_, _| Some((TRANCHE_TOKEN_PRICE, 1234)));
Pools::mock_currency_for(|_| Some(POOL_CURRENCY_ID));
MarketRatio::mock_market_ratio(|_, _| Err(DispatchError::Other("")));

assert_noop!(
LiquidityPools::update_token_price(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
CURRENCY_ID,
EVM_ADDRESS.domain(),
),
DispatchError::Other("")
);
})
}

#[test]
fn with_no_transferible_asset() {
System::externalities().execute_with(|| {
Pools::mock_get_price(|_, _| Some((TRANCHE_TOKEN_PRICE, 1234)));
Pools::mock_currency_for(|_| Some(POOL_CURRENCY_ID));
MarketRatio::mock_market_ratio(|_, _| Ok(MARKET_RATIO));
AssetRegistry::mock_metadata(|_| Some(util::default_metadata()));

assert_noop!(
LiquidityPools::update_token_price(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
CURRENCY_ID,
EVM_ADDRESS.domain(),
),
Error::<Runtime>::AssetNotLiquidityPoolsTransferable,
);
})
}
}
}

#[test]
fn receiving_output_message() {
System::externalities().execute_with(|| {
Expand Down
Loading

0 comments on commit e784233

Please sign in to comment.