Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Liquidity pools: Add UTs to for update_token_price() #1890

Merged
merged 11 commits into from
Jul 9, 2024
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>>;
}

Comment on lines -209 to -233
Copy link
Contributor Author

@lemunozm lemunozm Jun 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used, but if it's intended to be used very soon, I can revert this. Otherwise, I would left removed.

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lemunozm can we in the same run also close this todo and computed the conversion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's super straightforward. Do we need to add the order-book dependency to know the market price used for foreign/pool currencies?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it isn't. But we are not only touching the UTs here but also chaning the return types, naming etc. Then we could also close the todo IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here: 15424a8 @mustermeiszer

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks a lot!

.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
Loading