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

Loans: linear accrual on settlement price #1739

Merged
merged 17 commits into from
Feb 29, 2024
Merged

Conversation

lemunozm
Copy link
Contributor

@lemunozm lemunozm commented Feb 20, 2024

Description

Fixes #1738

Changes and Descriptions

  • Add current_price to RuntimeAPI loans portfolio for cases where the loan is external priced.
  • Update present value computation to use the current price instead of settlement price. (This present value affects portfolio computations)
  • Avoid erroring out in case T::PriceRegistry::collection() fails due to outdated collection.
  • Add UT for this case (Not added but fixed current failing test because of these changes)
  • Migration for the addition of settlement_price_updated field.

@lemunozm lemunozm added Q3-medium Can be done with good experience in the language, but little knowledge of the codebase. P7-asap Issue should be addressed in the next days. I8-enhancement An additional feature. D8-migration Pull request touches storage and needs migration code. labels Feb 20, 2024
@lemunozm lemunozm added this to the Centrifuge 1026 milestone Feb 20, 2024
@lemunozm lemunozm self-assigned this Feb 20, 2024
/// - If not set, then the price is a linear accrual using the latest
/// settlement price.
/// See [`ExternalActivePricing::current_price()`]
pub current_price: Option<T::Balance>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we could use the valuation method of the internal pricing here too. Removing the Option. ANd I would name it current_value.

cc: @denniswell

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Valuation method is just the present value for internal pricing loans. So we should do the same for external pricing loans too if go with this change.

This is related to my question:

Should we modify present_value() to now use current_price instead of latest_settlement_price?

For external pricing loans present_value = quantity * latest_settlemente_price. So maybe we do not need to use another field here and just update how present_value is computed using now current_price instead of latest_settlement_price

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah sorry, my bad! I think that is the best idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we still want to put current_price here to allow the UI just to draw the price. Otherwise, they should divide the present_value / outstanding_quantity to obtain it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that is confusing more ATM. The total value is anyways of greatest interest.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think all that is relevant mostly is the value anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding this topic, I would say that if Apps needs to draw the price, leave the current_price as it is. Otherwise, if they need it and we do not offer it by the RuntimeAPI, then they need to perform not-trivial computations, and future changes to present_value will break that.

@onnovisser are you drawing right now the settlementPrice of an external loan?

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 remove that one here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we decide after getting feedback from https://kflabs.slack.com/archives/C04GARZ532T/p1709024010720729 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Onno is out, which is who is taking care of it, I think, so probably we do not have feedback soon regarding this.

Seeing the apps code, they need to fetch the price as current_price now to fulfill this part: https://github.com/centrifuge/apps/blob/beea676a936a14597f270e931387a4df1ce7ca0d/centrifuge-app/src/utils/getLatestPrice.ts#L10 which is later used to draw in the loan page.

I would say we need to keep this here. Do you have any argument against this, @mustermeiszer? I do not see you very in favor of adding it.

pallets/loans/src/lib.rs Outdated Show resolved Hide resolved
Comment on lines +196 to 209
pub fn present_value_cached<Prices>(
&self,
cache: &Prices,
maturity: Seconds,
) -> Result<T::Balance, DispatchError>
where
Prices: DataCollection<T::PriceId, Data = PriceOf<T>>,
{
let price = match cache.get(&self.info.price_id) {
Ok(data) => data.0,
Err(_) => self.latest_settlement_price,
Err(_) => self.linear_accrual_price(maturity)?,
};
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}
Copy link
Contributor Author

@lemunozm lemunozm Feb 23, 2024

Choose a reason for hiding this comment

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

...the externally priced loan will fail to obtain the "real value" and will generate the missing price for itself using the linear accrual method.

Later, faking or not the update_portfolio() with a list of prices is independent of this process (probably in another PR where we change the PoolNAV trait).

Copy link
Collaborator

Choose a reason for hiding this comment

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

But this way we can not fake the oracle prices, in cases where we want to simulate the nav execution with new oracles. And this would mean closing an epoch is not protected if the oracle is outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But this way we can not fake the oracle prices

We can because when we fake them, the value will come from the Ok() branch (line 205).

Copy link
Contributor

Choose a reason for hiding this comment

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

Say since last epoch execution, no loan price was updated. IIUC, this PR does not affect the update_portfolio outcome. Asking because the PoolFees NAV is dependent on the Loans NAV.

Copy link
Contributor Author

@lemunozm lemunozm Feb 27, 2024

Choose a reason for hiding this comment

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

Well, it affects. Because now, when there are no oracles, the price is the linear accrual instead of settlement_price. Ideally, after this change, we should do a portfolio_update() to consolidate the changes in the present value affected by this.

We could even add it as part of the migration. WDYT @mustermeiszer?

Just calling to update the portfolio

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure I am following? Why should there be a problem for the PoolFees NAV?

Copy link
Contributor Author

@lemunozm lemunozm Feb 27, 2024

Choose a reason for hiding this comment

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

Not sure if it is for PoolFees. But it can be for pallet-loans because we are modifying how the present_value() is computed but we are not updating the portfolio with this new computation

Copy link
Contributor

Choose a reason for hiding this comment

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

@mustermeiszer The PoolFees NAV is dependent on the Loans NAV. So I wanted to check whether it is an issue if the Loans NAV is not updated with the present_value computation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But it can be for pallet-loans because we are modifying how the present_value() is computed but we are not updating the portfolio with this new computation

Finally added. If finally you think we shouldn't, I can revert it. Commit: b760ad6

@lemunozm
Copy link
Contributor Author

This PR is ready for review. It only needs the migration.

@lemunozm
Copy link
Contributor Author

lemunozm commented Feb 26, 2024

I was thinking that maybe it could be useful to pass prices to update_portfolio() as follows:

enum Prices {
    Custom(Collection), // Pass a custom collection with custom prices.
    FromRegistry, // Use the collection from the registry (it means from oracles). Standard behavior
    UsingLinearAccrual, // Use prices computed from `settlement_prices` with linear accrual. It can be useful if the collection is outdated and you want to pass "orientative" prices.
}

WDYT?

(would be in a following PR to not mix too many things here)

@mustermeiszer
Copy link
Collaborator

I would pass prices and stuff to a different api. simulate_epch_execution() as we also need to close the epoch in order to make this fully useful for the ui. Also the return-type is not just the nav, but a tuple of things, like tranche states, fulfillments, prices, etc.

@lemunozm
Copy link
Contributor Author

lemunozm commented Feb 26, 2024

I am not following. Do you mean to add a new extrinsic for faking prices? And leave the update_portfolio_valuation() extrinsic as it is?

@mustermeiszer
Copy link
Collaborator

I am not follo ing. Do you mean to add a new extrinsic for faking prices? And leave the update_portfolio_valuation() extrinsic as it is

no, I meant another runtime-api.

Copy link
Collaborator

@mustermeiszer mustermeiszer left a comment

Choose a reason for hiding this comment

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

Two changes I would like to see, but not blocking.

Will the adaption of the runtime-api to not error out happen in another PR?

/// - If not set, then the price is a linear accrual using the latest
/// settlement price.
/// See [`ExternalActivePricing::current_price()`]
pub current_price: Option<T::Balance>,
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 remove that one here?

pallets/loans/src/lib.rs Show resolved Hide resolved
@wischli wischli removed this from the Centrifuge 1026 milestone Feb 27, 2024
@wischli wischli added this to the Centrifuge 1025 milestone Feb 27, 2024
Copy link
Contributor

@wischli wischli left a comment

Choose a reason for hiding this comment

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

As always, looks really clean! I would like the migration to be done via OnRuntimeUpgrade instead of the current hooks approach.

libs/utils/src/lib.rs Show resolved Hide resolved
Comment on lines +196 to 209
pub fn present_value_cached<Prices>(
&self,
cache: &Prices,
maturity: Seconds,
) -> Result<T::Balance, DispatchError>
where
Prices: DataCollection<T::PriceId, Data = PriceOf<T>>,
{
let price = match cache.get(&self.info.price_id) {
Ok(data) => data.0,
Err(_) => self.latest_settlement_price,
Err(_) => self.linear_accrual_price(maturity)?,
};
Ok(self.outstanding_quantity.ensure_mul_int(price)?)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Say since last epoch execution, no loan price was updated. IIUC, this PR does not affect the update_portfolio outcome. Asking because the PoolFees NAV is dependent on the Loans NAV.

pallets/loans/src/lib.rs Show resolved Hide resolved
@lemunozm
Copy link
Contributor Author

Thanks! I guess you then agree with the two open threads @mustermeiszer

@lemunozm lemunozm merged commit e4180ea into main Feb 29, 2024
9 checks passed
@lemunozm lemunozm deleted the loans/linear-accrual-price branch February 29, 2024 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
D8-migration Pull request touches storage and needs migration code. I8-enhancement An additional feature. P7-asap Issue should be addressed in the next days. Q3-medium Can be done with good experience in the language, but little knowledge of the codebase.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feat: Linear Accrual on Settlement Price
3 participants