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

Apply and mint interest #85

Merged
merged 19 commits into from
Apr 4, 2024
Merged

Apply and mint interest #85

merged 19 commits into from
Apr 4, 2024

Conversation

bingen
Copy link
Collaborator

@bingen bingen commented Mar 27, 2024

Rebase of branch from #76 on top of branch from #81


We now have 3 aggregate trackers on the ActivePool, as per the simple compounding specification:

aggRecordedDebt (D)
recordedDebtSum (G)
weightedRecordedDebtSum (S)

G is the original ActivePool debt tracker from Liquity v1. We keep it for now as a) it may be useful for computing a size-weighted average interest rate on the front end, and b) would be needed if we decide to add an emergency base interest rate to every position.

Pseudocode for aggregate trackers in different operations

As in v1, we always apply any redistribution debt gain before handling interest accounting.
The redistribution gain is already included in G before we alter G from the specific operation.

The prefix “old” means the respective debt with no redistribution gains or interest applied, and “new” implies all debt changes (redist. gains, interest, debt drawn/repaid) are included.

openTrove:

D += agg_accrued_interest + trove_debt
S += weighted_trove_debt 
G += trove_debt

closeTrove:

D += (agg_accrued_interest - old_recorded_trove_debt - accrued_trove_interest) // Don’t remove redist. gain here since it was not already applied to D
S -= old_weighted_trove_debt
G -= (old_recorded_trove_debt + redistribution_gain)  // remove redist.gain here since it was already applied to G.

adjustTrove:

D+= agg_accrued_interest +trove_debt_change + redist_debt_gain
S+= (new_weighted_trove_debt - old_weighted_trove_debt)
G+= (new_recorded_trove_debt - old_recorded_trove_debt)

And for the individual Trove:
new_recorded_trove_debt = old_recorded_trove_debt + accrued_trove_interest + trove_debt_change

applyTroveInterestPermissionless:

D += agg_accrued_interest
S += (new_weighted_trove_debt - old_weighted_trove_debt)
G += (new_recorded_trove_debt - old_recorded_trove_debt)

adjustTroveInterestRate:

D+= agg_accrued_interest
S += (new_weighted_trove_debt - old_weighted_trove_debt)
G += (new_recorded_trove_debt - old_recorded_trove_debt)

SP deposit/withdrawal:
D += agg_accrued_interest

Liquidation - Normal Mode, offset

This is effectively the same as closing the Trove:

D+= agg_accrued_interest - old_recorded_trove_debt - accrued_trove_interest
S -= old_weighted_trove_debt
G -= (old_recorded_trove_debt + redist_debt_gain)

Liquidation - Normal Mode, redistribution:

This is effectively the same as closing the Trove, except we add the entire debt to the DefaultPool:

D+= (agg_accrued_interest - old_recorded_trove_debt - accrued_trove_interest)
S -= old_weighted_trove_debt
G -= (old_recorded_trove_debt + redist_debt_gain) 

Also:

DefaultPool.debt += (old_recorded_trove_debt + redistribution_gain + accrued_trove_interest)

TODOs:

** Liquidations in Recovery Mode**

Minting/applying interest has been implemented for RM liquidations to make tests pass, but if we keep RM we should double-check that minting/applying interest is correct for for every logical case of RM.

Redemption:

This is effectively a downward debt adjustment (since we will no longer close Troves on redemption):

D+= agg_accrued_interest - redeemed_debt + redist_debt_gain
S+= (new_weighted_recorded_debt - old_weighted_recorded_debt)
G+= (new_recorded_trove_debt - old_recorded_trove_debt)

This will be implemented in combination with #55 and #48, since these significantly change redemption logic anyway.

Testing TODOs:

  • more thorough invariant test (i.e. D + S*delta_t == sum_individual_entire_trove_debts)

@bingen bingen self-assigned this Mar 27, 2024
@bingen bingen force-pushed the apply_and_mint_interest_rebase branch from a68384f to 2e111ce Compare March 27, 2024 13:30
Copy link
Collaborator Author

@bingen bingen left a comment

Choose a reason for hiding this comment

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

My first batch of comments. Mostly I think I would try to refactor a bit, so that we unify calls to the ActivePool and consolidate all the logic there, but not sure if that’s possible, or if the result would have a lot of if-else that would make things worse.

// It does *not* include the Trove's individual accrued interest - this gets accounted for in the aggregate accrued interest.
// The net Trove debt change could be positive or negative in a repayment (depending on whether its redistribution gain or repayment amount is larger),
// so this function accepts both the increase and the decrease to avoid using (and converting to/from) signed ints.
function mintAggInterest(uint256 _troveDebtIncrease, uint256 _troveDebtDecrease) public {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it would be a good idea to merge this function with the ones for G (increase/decrease recorded debt).
At least the external end point (we can keep internally the functions as they are). For 2 reasons:

  • Gas efficiency (only 1 call)
  • Less prone to error, to make sure both trackers are consistent.
    I wonder if we could even have S there too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As I see there are calls with (0,0) as argument, maybe we could have a public function mintAggInterestNoDebtChange that would be called by the more generic mintAggInterest which also calls the increaseBoldDebt and decreaseBoldDebt.
This generic one could also accept the accrued interest as a parameter, to update S if non-zero.

Copy link
Collaborator

@RickGriff RickGriff Apr 4, 2024

Choose a reason for hiding this comment

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

I've refactored the D, G and S updates into a single call in this PR. Let me know what you think. It's still a bit unintuitive. This should get simpler if/when we optimize redistribution gains - then we can just update each tracker once with all the changes (debt change, accrued interest, pending gains).


vars.BoldFee;
vars.netDebt = _boldAmount;
vars.BoldFee; // TODO
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This can be removed, right? Or is it for the initial period pre-payment?

}

// --- Effects & interactions ---

contractsCache.activePool.mintAggInterest(vars.compositeDebt, 0);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Even if we don’t merge the endpoints, I would put the 3 calls to ActivePool together in this function.

@bingen bingen mentioned this pull request Apr 3, 2024
Copy link
Collaborator Author

@bingen bingen left a comment

Choose a reason for hiding this comment

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

Some more minor comments and doubts I had left for TroveManager.

return Troves[_troveId].lastDebtUpdateTime;
}

function troveIsStale(uint256 _troveId) external view returns (bool) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What is this used for?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Currently only in Foundry tests - so it doesn't strictly need to be in core contracts, though I recall the ToB audit recommending we make convenience getters like this part of the core system. I don't mind removing it though if you prefer.

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, it’s fine then. It will only mean some little amount of gas for us deploying, so let’s leave it if it’s useful.

What was making me wonder is that the constant for the stale time is not used anywhere else, right? So this getter seems like meaningless or arbitrary. I want to make sure I’m not missing anything.

@@ -74,7 +77,8 @@ contract TroveManager is ERC721, LiquityBase, Ownable, CheckContract, ITroveMana
uint stake;
Status status;
uint128 arrayIndex;
uint256 annualInterestRate;
uint256 annualInterestRate;
uint64 lastDebtUpdateTime;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think just moving lastDebtUpdateTime one position up would save gas, as it would packed with status and arrayIndex. And we wouldn’t touch the original v1 vars.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice, sounds good

@@ -475,6 +500,8 @@ contract TroveManager is ERC721, LiquityBase, Ownable, CheckContract, ITroveMana
(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unrelated to this PR, but it seems that the comment “..., and close the trove” above is outdated. We may take the opportunity to fix it.


// TODO - Gas: combine these into one call to activePool?
// Remove the liquidated recorded debt from the sum
activePoolCached.decreaseRecordedDebtSum(totals.totalRecordedDebtInSequence);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

According to the spec, aren’t we missing redistribution gains here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I think you're right. Good catch!


return (currentETH, currentBoldDebt);
}

function applyPendingRewards(uint256 _troveId) external override {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Are we removing the override for any particular reason?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah no that's an oversight (I guess you mean in the new getAndApplyRedistributionGains). Added it back in.

Troves[_troveId].coll = Troves[_troveId].coll + pendingETHReward;
Troves[_troveId].debt = Troves[_troveId].debt + pendingBoldDebtReward;

_updateTroveRewardSnapshots(_troveId);

// Transfer from DefaultPool to ActivePool
//TODO: Update agg. recorded debt and agg. weighted debt sum with the redistribution gains
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What’s the point of this? Is it just to have more up to date values? This function is inside a loop in redeemCollateral, so that may not be so efficient.

Copy link
Collaborator

@RickGriff RickGriff Apr 4, 2024

Choose a reason for hiding this comment

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

Hmm AFAIK this is a relic and not relevant. I'll delete. As mentioned above, eventually we would hopefully do away with separately applying redist. gains, and update the trackers with all changes in one go.

entireColl = recordedColl + pendingETHReward;
}

function getTroveEntireDebt(uint256 _troveId) public view returns (uint256) {
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 big deal, but these ones can be converted to external.

@bingen bingen changed the base branch from trove_ERC721 to main April 4, 2024 08:31
@bingen bingen marked this pull request as ready for review April 4, 2024 15:01
@bingen bingen merged commit a3e5d0c into main Apr 4, 2024
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants