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

Fix/linear pricing #1812

Merged
merged 25 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3df5900
feat: restrict epoch closing to liquiduty admin
mustermeiszer Apr 15, 2024
e004106
fix: linear pricing
mustermeiszer Apr 15, 2024
7b4ab74
proof: type unsafety
mustermeiszer Apr 15, 2024
9ad611b
revert: type unsafety
mustermeiszer Apr 15, 2024
9dc56e5
fix: tests and adapt logic to allow dyanmic input
mustermeiszer Apr 16, 2024
ca5ab9d
Merge branch 'main' into fix/linear-pricing
mustermeiszer Apr 16, 2024
c002271
chore: generic removal
mustermeiszer Apr 16, 2024
ec1a96b
chore: cleaner logic
mustermeiszer Apr 16, 2024
2c2edc0
fix: Clippy you annoying thing
mustermeiszer Apr 16, 2024
db35635
fix: cliiiiiippyyyyy
mustermeiszer Apr 16, 2024
b1ef424
fix: cliippyy v1m
mustermeiszer Apr 16, 2024
b4854b1
feat: make PoolAdmin configurable
mustermeiszer May 6, 2024
2aca1dd
fix: benchmarks
mustermeiszer May 6, 2024
c978951
feat: make pricing linear optional
mustermeiszer May 6, 2024
e2dd3ca
Merge remote-tracking branch 'origin/main' into fix/linear-pricing
mustermeiszer May 6, 2024
9265bbb
Fix: linear accrual not overpassing maturity (#1842)
lemunozm May 23, 2024
967584d
increment coverage when loan is at maturity date
lemunozm May 23, 2024
2a691e9
fix division_by_zero issue
lemunozm May 23, 2024
45be927
feat: fix linear pricing migration (#1838)
wischli May 23, 2024
8c01edc
Merge remote-tracking branch 'origin/main' into fix/linear-pricing
mustermeiszer May 24, 2024
3001d9d
fix: make mock accept all origins
mustermeiszer May 24, 2024
b831f99
feat: add test for switching between oracle or settlement price
mustermeiszer May 24, 2024
5fee771
fix: not needed where clause
mustermeiszer May 24, 2024
fcddabf
feat: move additional test
mustermeiszer May 24, 2024
b6976c8
fix: nits
mustermeiszer May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pallets/loans/src/entities/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
entities::pricing::external::ExternalAmount,
pallet::{Config, Error},
types::RepaidAmount,
PriceOf,
};

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
Expand Down Expand Up @@ -61,6 +62,6 @@ impl<T: Config> RepaidInput<T> {
#[scale_info(skip_type_params(T))]
pub enum PriceCollectionInput<T: Config> {
Empty,
Custom(BoundedBTreeMap<T::PriceId, T::Balance, T::MaxActiveLoansPerPool>),
Custom(BoundedBTreeMap<T::PriceId, PriceOf<T>, T::MaxActiveLoansPerPool>),
FromRegistry,
}
3 changes: 2 additions & 1 deletion pallets/loans/src/entities/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::{
BorrowLoanError, BorrowRestrictions, CloseLoanError, CreateLoanError, LoanRestrictions,
MutationError, RepaidAmount, RepayLoanError, RepayRestrictions, RepaymentSchedule,
},
PriceOf,
};

/// Loan information.
Expand Down Expand Up @@ -290,7 +291,7 @@ impl<T: Config> ActiveLoan<T> {
pub fn present_value_by<Rates>(
&self,
rates: &Rates,
prices: &BTreeMap<T::PriceId, T::Balance>,
prices: &BTreeMap<T::PriceId, PriceOf<T>>,
) -> Result<T::Balance, DispatchError>
where
Rates: RateCollection<T::Rate, T::Balance, T::Balance>,
Expand Down
41 changes: 30 additions & 11 deletions pallets/loans/src/entities/pricing/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use sp_std::collections::btree_map::BTreeMap;
use crate::{
entities::interest::ActiveInterestRate,
pallet::{Config, Error},
PriceOf,
};

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen)]
Expand Down Expand Up @@ -150,9 +151,14 @@ impl<T: Config> ExternalActivePricing<T> {
}
}

fn linear_accrual_price(&self, maturity: Seconds) -> Result<T::Balance, DispatchError> {
fn linear_accrual_price(
&self,
maturity: Seconds,
price: T::Balance,
price_last_updated: Seconds,
) -> Result<T::Balance, DispatchError> {
Ok(cfg_utils::math::y_coord_in_rect(
(self.settlement_price_updated, self.latest_settlement_price),
(price_last_updated, price),
lemunozm marked this conversation as resolved.
Show resolved Hide resolved
(maturity, self.info.notional),
T::Time::now(),
)?)
Expand All @@ -163,10 +169,26 @@ impl<T: Config> ExternalActivePricing<T> {
pool_id: T::PoolId,
maturity: Seconds,
) -> Result<T::Balance, DispatchError> {
Ok(match T::PriceRegistry::get(&self.info.price_id, &pool_id) {
Ok(data) => data.0,
Err(_) => self.linear_accrual_price(maturity)?,
})
self.current_price_inner(
maturity,
T::PriceRegistry::get(&self.info.price_id, &pool_id).ok(),
)
}

fn current_price_inner(
Copy link
Contributor

Choose a reason for hiding this comment

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

The current on-chain behavior is:

  • If oracle is found, use oracle price
  • If not found, use linear accrual price.

That behavior can not be modeled right now. We need to choose between:

  • Use oracle price or if not use settlement price (with_linear_pricing = false)
  • Use always linear accrual price (with_linear_pricing = true)

Do we want this? Don't we still want to model the current behavior?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, we want to change that too.

Get the price:

  • IF oracle → use oracle
  • ELSE → use settlement price

Use the price:

  • IF linear pricing → use linear pricing
  • ELSE → just use price

Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between "Get the price" and "Use the price"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Get is either orale or settlement. Use is just for using whatever came out of the get.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would add new 2 test cases to check a loan configured with use_linear_price = true always uses the linear accrual with oracle value and with settlement price

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, after the changes of this PR all loans comes with use_linear_price = true, so we should check the false version instead

&self,
maturity: Seconds,
oracle: Option<PriceOf<T>>,
) -> Result<T::Balance, DispatchError> {
if let Some((oracle_price, oracle_provided_at)) = oracle {
self.linear_accrual_price(maturity, oracle_price, oracle_provided_at.into_seconds())
mustermeiszer marked this conversation as resolved.
Show resolved Hide resolved
} else {
self.linear_accrual_price(
maturity,
self.latest_settlement_price,
self.settlement_price_updated,
)
}
}

pub fn outstanding_principal(
Expand Down Expand Up @@ -197,13 +219,10 @@ impl<T: Config> ExternalActivePricing<T> {

pub fn present_value_cached(
&self,
cache: &BTreeMap<T::PriceId, T::Balance>,
cache: &BTreeMap<T::PriceId, PriceOf<T>>,
maturity: Seconds,
) -> Result<T::Balance, DispatchError> {
let price = match cache.get(&self.info.price_id) {
Some(data) => *data,
None => self.linear_accrual_price(maturity)?,
};
let price = self.current_price_inner(maturity, cache.get(&self.info.price_id).copied())?;
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}

Expand Down
6 changes: 3 additions & 3 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub mod pallet {
type Time: TimeAsSecs;

/// Generic time type
type Moment: Parameter + Member + IntoSeconds;
type Moment: Parameter + Member + Copy + IntoSeconds;

/// Used to mint, transfer, and inspect assets.
type NonFungible: Transfer<Self::AccountId>
Expand Down Expand Up @@ -1050,15 +1050,15 @@ pub mod pallet {

pub fn registered_prices(
pool_id: T::PoolId,
) -> Result<BTreeMap<T::PriceId, T::Balance>, DispatchError> {
) -> Result<BTreeMap<T::PriceId, PriceOf<T>>, DispatchError> {
let collection = T::PriceRegistry::collection(&pool_id)?;
Ok(ActiveLoans::<T>::get(pool_id)
.iter()
.filter_map(|(_, loan)| loan.price_id())
.filter_map(|price_id| {
collection
.get(&price_id)
.map(|price| (price_id, price.0))
.map(|price| (price_id, (price.0, price.1)))
.ok()
})
.collect::<BTreeMap<_, _>>())
Expand Down
6 changes: 4 additions & 2 deletions pallets/loans/src/tests/portfolio_valuation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,16 @@ fn with_unregister_price_id_and_oracle_not_required() {
expected_portfolio(QUANTITY.saturating_mul_int(price_value_after_half_year));

// Suddenty, the oracle set a value
const MARKET_PRICE_VALUE: Balance = 999;
MockPrices::mock_collection(|_| {
Ok(MockDataCollection::new(|_| {
Ok((PRICE_VALUE * 8, BLOCK_TIME_MS))
Ok((MARKET_PRICE_VALUE, BLOCK_TIME_MS))
}))
});
let price_value_after_half_year = MARKET_PRICE_VALUE + (NOTIONAL - MARKET_PRICE_VALUE) / 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

I like more how you have tested this!


update_portfolio();
expected_portfolio(QUANTITY.saturating_mul_int(PRICE_VALUE * 8));
expected_portfolio(QUANTITY.saturating_mul_int(price_value_after_half_year));
});
}

Expand Down
20 changes: 18 additions & 2 deletions pallets/pool-system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,15 @@ pub mod pallet {
#[transactional]
#[pallet::call_index(1)]
pub fn close_epoch(origin: OriginFor<T>, pool_id: T::PoolId) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let who = ensure_signed(origin)?;
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who,
Role::PoolRole(PoolRole::LiquidityAdmin)
),
BadOrigin
);

Pool::<T>::try_mutate(pool_id, |pool| {
let pool = pool.as_mut().ok_or(Error::<T>::NoSuchPool)?;
Expand Down Expand Up @@ -876,7 +884,15 @@ pub mod pallet {
origin: OriginFor<T>,
pool_id: T::PoolId,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let who = ensure_signed(origin)?;
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who,
Role::PoolRole(PoolRole::LiquidityAdmin)
),
BadOrigin
);

EpochExecution::<T>::try_mutate(pool_id, |epoch_info| {
let epoch = epoch_info
Expand Down
12 changes: 5 additions & 7 deletions runtime/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod xcm;

use cfg_primitives::Balance;
use cfg_types::{fee_keys::FeeKey, pools::PoolNav, tokens::CurrencyId};
use frame_support::BoundedBTreeMap;
use orml_traits::GetByKey;
use pallet_loans::entities::input::PriceCollectionInput;
use pallet_pool_system::Nav;
Expand All @@ -37,7 +38,7 @@ use sp_runtime::{
traits::{Get, Zero},
DispatchError,
};
use sp_std::marker::PhantomData;
use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData};

parameter_types! {
/// The native currency identifier of our currency id enum
Expand Down Expand Up @@ -102,12 +103,9 @@ where
>,
{
let input_prices: PriceCollectionInput<T> =
if let Ok(prices) = pallet_loans::Pallet::<T>::registered_prices(pool_id) {
PriceCollectionInput::Custom(prices.try_into().map_err(|_| {
DispatchError::Other("Map expected to fit as it is coming from loans itself.")
})?)
} else {
PriceCollectionInput::Empty
match pallet_loans::Pallet::<T>::registered_prices(pool_id) {
Ok(_) => PriceCollectionInput::FromRegistry,
Err(_) => PriceCollectionInput::Empty,
};

update_nav_with_input::<T>(pool_id, input_prices)
Expand Down
Loading