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

Leave redeemed Troves open and apply & mint interest at redemption #101

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 48 additions & 20 deletions contracts/src/ActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -173,24 +173,26 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
emit ActivePoolETHBalanceUpdated(newETHBalance);
}

function increaseRecordedDebtSum(uint _amount) external override {
_requireCallerIsBOorTroveM();
uint256 newRecordedDebtSum = recordedDebtSum + _amount;
recordedDebtSum = newRecordedDebtSum;
emit ActivePoolBoldDebtUpdated(newRecordedDebtSum);
function increaseRecordedDebtSum(uint256 _amount) external {
_requireCallerIsTroveManager();
_changeRecordedDebtSum(_amount, 0);
}

function decreaseRecordedDebtSum(uint _amount) external override {
_requireCallerIsBOorTroveMorSP();
uint256 newRecordedDebtSum = recordedDebtSum - _amount;

recordedDebtSum = newRecordedDebtSum;
// TODO: remove this once we implement interest minting in redemptions
function decreaseRecordedDebtSum(uint256 _amount) external {
_requireCallerIsTroveManager();
_changeRecordedDebtSum(0, _amount);
}

function _changeRecordedDebtSum(uint256 _recordedDebtIncrease, uint256 _recordedDebtDecrease) internal {
// Do the arithmetic in 2 steps here to avoid overflow from the decrease
uint256 newRecordedDebtSum = recordedDebtSum + _recordedDebtIncrease; // 1 SLOAD
newRecordedDebtSum -= _recordedDebtDecrease;
recordedDebtSum = newRecordedDebtSum; // 1 SSTORE
emit ActivePoolBoldDebtUpdated(newRecordedDebtSum);
}

function changeAggWeightedDebtSum(uint256 _oldWeightedRecordedTroveDebt, uint256 _newTroveWeightedRecordedTroveDebt) external {
_requireCallerIsBOorTroveM();
function _changeAggWeightedDebtSum( uint256 _newTroveWeightedRecordedTroveDebt, uint256 _oldWeightedRecordedTroveDebt) internal {
// Do the arithmetic in 2 steps here to avoid overflow from the decrease
uint256 newAggWeightedDebtSum = aggWeightedDebtSum + _newTroveWeightedRecordedTroveDebt; // 1 SLOAD
newAggWeightedDebtSum -= _oldWeightedRecordedTroveDebt;
Expand All @@ -206,21 +208,42 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
// 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 {
_requireCallerIsBOorTroveMorSP();
uint256 aggInterest = calcPendingAggInterest();
// Mint the new BOLD interest to a mock interest router that would split it and send it onward to SP, LP staking, etc.
// TODO: implement interest routing and SP Bold reward tracking
if (aggInterest > 0) {boldToken.mint(address(interestRouter), aggInterest);}

function mintAggInterest(
uint256 _troveDebtIncrease,
uint256 _troveDebtDecrease,
uint256 recordedSumIncrease,
uint256 recordedSumDecrease,
uint256 newWeightedRecordedTroveDebt,
uint256 oldWeightedRecordedTroveDebt
)
external
{
_requireCallerIsBOorTroveM();

// Do the arithmetic in 2 steps here to avoid overflow from the decrease
uint256 newAggRecordedDebt = aggRecordedDebt + aggInterest + _troveDebtIncrease; // 1 SLOAD
uint256 newAggRecordedDebt = _mintAggInterestNoTroveChange() + _troveDebtIncrease; // 1 SLOAD
newAggRecordedDebt -=_troveDebtDecrease;
aggRecordedDebt = newAggRecordedDebt; // 1 SSTORE
// assert(aggRecordedDebt >= 0) // This should never be negative. If all redistribution gians and all aggregate interest was applied
// and all Trove debts were repaid, it should become 0.

_changeRecordedDebtSum(recordedSumIncrease, recordedSumDecrease);
_changeAggWeightedDebtSum(newWeightedRecordedTroveDebt, oldWeightedRecordedTroveDebt);
}

function mintAggInterestNoTroveChange() external returns (uint256) {
_requireCallerIsSP();
aggRecordedDebt = _mintAggInterestNoTroveChange();
}

function _mintAggInterestNoTroveChange() internal returns (uint256) {
uint256 aggInterest = calcPendingAggInterest();
// Mint the new BOLD interest to a mock interest router that would split it and send it onward to SP, LP staking, etc.
// TODO: implement interest routing and SP Bold reward tracking
if (aggInterest > 0) {boldToken.mint(address(interestRouter), aggInterest);}

lastAggUpdateTime = block.timestamp;
return aggRecordedDebt + aggInterest;
}

// --- 'require' functions ---
Expand All @@ -240,6 +263,11 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
"ActivePool: Caller is neither BorrowerOperations nor TroveManager nor StabilityPool");
}

function _requireCallerIsSP() internal view {
require(
msg.sender == stabilityPoolAddress, "ActivePool: Caller is not StabilityPool");
}

function _requireCallerIsBOorTroveM() internal view {
require(
msg.sender == borrowerOperationsAddress ||
Expand Down
90 changes: 48 additions & 42 deletions contracts/src/BorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
uint newEntireDebt;
uint newEntireColl;
uint stake;
uint256 initialWeightedRecordedTroveDebt;
uint256 newWeightedTroveDebt;
uint256 annualInterestRate;
uint256 troveDebtIncrease;
uint256 troveDebtDecrease;
uint256 recordedDebtIncrease;
uint256 recordedDebtDecrease;
}

struct LocalVariables_openTrove {
Expand Down Expand Up @@ -202,7 +209,8 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

// --- Effects & interactions ---

contractsCache.activePool.mintAggInterest(vars.compositeDebt, 0);
uint256 weightedRecordedTroveDebt = vars.compositeDebt * _annualInterestRate;
contractsCache.activePool.mintAggInterest(vars.compositeDebt, 0, vars.compositeDebt, 0, weightedRecordedTroveDebt, 0);

// Set the stored Trove properties and mint the NFT
vars.stake = contractsCache.troveManager.setTrovePropertiesOnOpen(
Expand All @@ -224,11 +232,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
contractsCache.boldToken.mint(msg.sender, _boldAmount);
contractsCache.boldToken.mint(gasPoolAddress, BOLD_GAS_COMPENSATION);

// Add the whole debt to the recorded debt tracker
contractsCache.activePool.increaseRecordedDebtSum(vars.compositeDebt);
// Add the whole weighted debt to the weighted recorded debt tracker
contractsCache.activePool.changeAggWeightedDebtSum(0, vars.compositeDebt * _annualInterestRate);

emit TroveUpdated(troveId, vars.compositeDebt, _ETHAmount, vars.stake, BorrowerOperation.openTrove);
emit BoldBorrowingFeePaid(troveId, vars.BoldFee); // TODO

Expand Down Expand Up @@ -315,8 +318,8 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

vars.price = priceFeed.fetchPrice();

uint256 initialWeightedRecordedTroveDebt = contractsCache.troveManager.getTroveWeightedRecordedDebt(_troveId);
uint256 annualInterestRate = contractsCache.troveManager.getTroveAnnualInterestRate(_troveId);
vars.initialWeightedRecordedTroveDebt = contractsCache.troveManager.getTroveWeightedRecordedDebt(_troveId);
vars.annualInterestRate = contractsCache.troveManager.getTroveAnnualInterestRate(_troveId);

// --- Checks ---

Expand Down Expand Up @@ -364,14 +367,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

contractsCache.troveManager.getAndApplyRedistributionGains(_troveId);

if (_isDebtIncrease) {
// Increase Trove debt by the drawn debt + redist. gain
activePool.mintAggInterest(_boldChange + vars.redistDebtGain, 0);
} else {
// Increase Trove debt by redist. gain and decrease by the repaid debt
activePool.mintAggInterest(vars.redistDebtGain, _boldChange);
}

// Update the Trove's recorded coll and debt
vars.newEntireColl = _updateTroveCollFromAdjustment(
contractsCache.troveManager,
Expand All @@ -393,6 +388,30 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

vars.stake = contractsCache.troveManager.updateStakeAndTotalStakes(_troveId);

vars.newWeightedTroveDebt = vars.newEntireDebt * vars.annualInterestRate;

if (_isDebtIncrease) {
// Increase Trove debt by the drawn debt + redist. gain
vars.troveDebtIncrease = _boldChange + vars.redistDebtGain;
vars.recordedDebtIncrease = _boldChange + vars.accruedTroveInterest;
} else {
// Increase Trove debt by redist. gain and decrease by the repaid debt
vars.troveDebtIncrease = vars.redistDebtGain;
vars.troveDebtDecrease = _boldChange;

vars.recordedDebtIncrease = vars.accruedTroveInterest;
vars.recordedDebtDecrease = _boldChange;
}

activePool.mintAggInterest(
vars.troveDebtIncrease,
vars.troveDebtDecrease,
vars.recordedDebtIncrease,
vars.recordedDebtDecrease,
vars.newWeightedTroveDebt,
vars.initialWeightedRecordedTroveDebt
);

emit TroveUpdated(_troveId, vars.newEntireDebt, vars.newEntireColl, vars.stake, BorrowerOperation.adjustTrove);
emit BoldBorrowingFeePaid(_troveId, vars.BoldFee); // TODO

Expand All @@ -407,8 +426,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
_isDebtIncrease,
vars.accruedTroveInterest
);

contractsCache.activePool.changeAggWeightedDebtSum(initialWeightedRecordedTroveDebt, vars.newEntireDebt * annualInterestRate);
}

function closeTrove(uint256 _troveId) external override {
Expand Down Expand Up @@ -445,19 +462,17 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

// Remove the Trove's initial recorded debt plus its accrued interest from ActivePool.aggRecordedDebt,
// but *don't* remove the redistribution gains, since these were not yet incorporated into the sum.
contractsCache.activePool.mintAggInterest(0, initialRecordedTroveDebt + accruedTroveInterest);

contractsCache.troveManager.removeStake(_troveId);
contractsCache.troveManager.closeTrove(_troveId);
emit TroveUpdated(_troveId, 0, 0, 0, BorrowerOperation.closeTrove);

uint256 troveDebtDecrease = initialRecordedTroveDebt + accruedTroveInterest;
// Remove only the Trove's latest recorded debt (inc. redist. gains) from the recorded debt tracker,
// i.e. exclude the accrued interest since it has not been added.
// TODO: If/when redist. gains are gas-optimized, exclude them from here too.
contractsCache.activePool.decreaseRecordedDebtSum(initialRecordedTroveDebt + debtRedistGain);
uint256 recordedDebtSumDecrease = initialRecordedTroveDebt + debtRedistGain;

// Remove Trove's weighted debt from the weighted sum
activePool.changeAggWeightedDebtSum(initialWeightedRecordedTroveDebt, 0);
contractsCache.activePool.mintAggInterest(0, troveDebtDecrease, 0, recordedDebtSumDecrease, 0, initialWeightedRecordedTroveDebt);

contractsCache.troveManager.removeStake(_troveId);
contractsCache.troveManager.closeTrove(_troveId);
emit TroveUpdated(_troveId, 0, 0, 0, BorrowerOperation.closeTrove);

// Burn the 200 BOLD gas compensation
contractsCache.boldToken.burn(gasPoolAddress, BOLD_GAS_COMPENSATION);
Expand All @@ -478,7 +493,7 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

uint256 entireTroveDebt = _updateActivePoolTrackersNoDebtChange(contractsCache.troveManager, contractsCache.activePool, _troveId, annualInterestRate);

// Update Trove recorded debt and interest-weighted debt sum
// Update Trove recorded debt
contractsCache.troveManager.updateTroveDebtFromInterestApplication(_troveId, entireTroveDebt);
}

Expand Down Expand Up @@ -596,14 +611,9 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
internal
{
if (_isDebtIncrease) {
_activePool.increaseRecordedDebtSum(_boldChange + _accruedTroveInterest);
address borrower = _troveManager.ownerOf(_troveId);
_boldToken.mint(borrower, _boldChange);
} else {
// TODO: Gas optimize this
_activePool.increaseRecordedDebtSum(_accruedTroveInterest);
_activePool.decreaseRecordedDebtSum(_boldChange);

_boldToken.burn(msg.sender, _boldChange);
}

Expand Down Expand Up @@ -639,19 +649,15 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

(, uint256 redistDebtGain) = _troveManager.getAndApplyRedistributionGains(_troveId);

// No debt is issued/repaid, so the net Trove debt change is purely the redistribution gain
_activePool.mintAggInterest(redistDebtGain, 0);

uint256 accruedTroveInterest = _troveManager.calcTroveAccruedInterest(_troveId);
uint256 recordedTroveDebt = _troveManager.getTroveDebt(_troveId);
uint256 entireTroveDebt = recordedTroveDebt + accruedTroveInterest;

uint256 newWeightedTroveDebt = entireTroveDebt * _annualInterestRate;
// Add only the Trove's accrued interest to the recorded debt tracker since we have already applied redist. gains.
// TODO: include redist. gains here if we gas-optimize them
_activePool.increaseRecordedDebtSum(accruedTroveInterest);
// Remove the old weighted recorded debt and and add the new one to the relevant tracker
_activePool.changeAggWeightedDebtSum(initialWeightedRecordedTroveDebt, entireTroveDebt * _annualInterestRate);

// No debt is issued/repaid, so the net Trove debt change is purely the redistribution gain
// TODO: also include redist. gains here in the recordedSumIncrease arg if we gas-optimize them
_activePool.mintAggInterest(redistDebtGain, 0, accruedTroveInterest, 0, newWeightedTroveDebt, initialWeightedRecordedTroveDebt);

return entireTroveDebt;
}

Expand Down
13 changes: 9 additions & 4 deletions contracts/src/Interfaces/IActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ interface IActivePool {
function aggWeightedDebtSum() external view returns (uint256);
function calcPendingAggInterest() external view returns (uint256);

function mintAggInterest(uint256 _troveDebtIncrease, uint256 _troveDebtDecrease) external;
function changeAggWeightedDebtSum(
uint256 _oldWeightedRecordedTroveDebt,
uint256 _newTroveWeightedRecordedTroveDebt
function mintAggInterest(
uint256 _troveDebtIncrease,
uint256 _troveDebtDecrease,
uint256 recordedSumIncrease,
uint256 recordedSumDecrease,
uint256 newWeightedRecordedTroveDebt,
uint256 oldWeightedRecordedTroveDebt
) external;

function mintAggInterestNoTroveChange() external returns (uint256);
function increaseRecordedDebtSum(uint256 _amount) external;
function decreaseRecordedDebtSum(uint256 _amount) external;
function sendETH(address _account, uint _amount) external;
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/StabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ contract StabilityPool is LiquityBase, Ownable, CheckContract, IStabilityPool {
function provideToSP(uint _amount) external override {
_requireNonZeroAmount(_amount);

activePool.mintAggInterest(0, 0);
activePool.mintAggInterestNoTroveChange();

uint initialDeposit = deposits[msg.sender].initialValue;

Expand Down Expand Up @@ -318,7 +318,7 @@ contract StabilityPool is LiquityBase, Ownable, CheckContract, IStabilityPool {
uint initialDeposit = deposits[msg.sender].initialValue;
_requireUserHasDeposit(initialDeposit);

activePool.mintAggInterest(0, 0);
activePool.mintAggInterestNoTroveChange();

uint depositorETHGain = getDepositorETHGain(msg.sender);

Expand Down
Loading
Loading