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

Add borrower-chosen interest rate ordering #63

Merged
merged 9 commits into from
Feb 20, 2024
97 changes: 49 additions & 48 deletions contracts/src/BorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
uint netDebt;
uint compositeDebt;
uint ICR;
uint NICR;
uint stake;
uint arrayIndex;
}
Expand Down Expand Up @@ -145,16 +144,19 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe

// --- Borrower Trove Operations ---

function openTrove(uint _maxFeePercentage, uint _boldAmount, address _upperHint, address _lowerHint) external payable override {
function openTrove(uint _maxFeePercentage, uint _boldAmount, address _upperHint, address _lowerHint, uint256 _annualInterestRate) external payable override {
ContractsCache memory contractsCache = ContractsCache(troveManager, activePool, boldToken);
LocalVariables_openTrove memory vars;

vars.price = priceFeed.fetchPrice();
bool isRecoveryMode = _checkRecoveryMode(vars.price);

_requireValidAnnualInterestRate(_annualInterestRate);
_requireValidMaxFeePercentage(_maxFeePercentage, isRecoveryMode);
_requireTroveisNotActive(contractsCache.troveManager, msg.sender);

// TODO: apply aggregate pending interest, and take snapshot of current timestamp.

vars.BoldFee;
vars.netDebt = _boldAmount;

Expand All @@ -168,7 +170,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
assert(vars.compositeDebt > 0);

vars.ICR = LiquityMath._computeCR(msg.value, vars.compositeDebt, vars.price);
vars.NICR = LiquityMath._computeNominalCR(msg.value, vars.compositeDebt);

if (isRecoveryMode) {
_requireICRisAboveCCR(vars.ICR);
Expand All @@ -178,15 +179,15 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
_requireNewTCRisAboveCCR(newTCR);
}

// Set the trove struct's properties
contractsCache.troveManager.setTroveStatus(msg.sender, 1);
contractsCache.troveManager.increaseTroveColl(msg.sender, msg.value);
contractsCache.troveManager.increaseTroveDebt(msg.sender, vars.compositeDebt);

contractsCache.troveManager.updateTroveRewardSnapshots(msg.sender);
vars.stake = contractsCache.troveManager.updateStakeAndTotalStakes(msg.sender);
// Set the stored Trove properties
vars.stake = contractsCache.troveManager.setTrovePropertiesOnOpen(
bingen marked this conversation as resolved.
Show resolved Hide resolved
msg.sender,
msg.value,
vars.compositeDebt,
_annualInterestRate
);

sortedTroves.insert(msg.sender, vars.NICR, _upperHint, _lowerHint);
sortedTroves.insert(msg.sender, _annualInterestRate, _upperHint, _lowerHint);
vars.arrayIndex = contractsCache.troveManager.addTroveOwnerToArray(msg.sender);
emit TroveCreated(msg.sender, vars.arrayIndex);

Expand All @@ -201,33 +202,49 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
}

// Send ETH as collateral to a trove
function addColl(address _upperHint, address _lowerHint) external payable override {
_adjustTrove(msg.sender, 0, 0, false, _upperHint, _lowerHint, 0);
function addColl() external payable override {
_adjustTrove(msg.sender, 0, 0, false, 0);
}

// Send ETH as collateral to a trove. Called by only the Stability Pool.
function moveETHGainToTrove(address _borrower, address _upperHint, address _lowerHint) external payable override {
function moveETHGainToTrove(address _borrower) external payable override {
_requireCallerIsStabilityPool();
_adjustTrove(_borrower, 0, 0, false, _upperHint, _lowerHint, 0);
_adjustTrove(_borrower, 0, 0, false, 0);
}

// Withdraw ETH collateral from a trove
function withdrawColl(uint _collWithdrawal, address _upperHint, address _lowerHint) external override {
_adjustTrove(msg.sender, _collWithdrawal, 0, false, _upperHint, _lowerHint, 0);
function withdrawColl(uint _collWithdrawal) external override {
_adjustTrove(msg.sender, _collWithdrawal, 0, false, 0);
}

// Withdraw Bold tokens from a trove: mint new Bold tokens to the owner, and increase the trove's debt accordingly
function withdrawBold(uint _maxFeePercentage, uint _boldAmount, address _upperHint, address _lowerHint) external override {
_adjustTrove(msg.sender, 0, _boldAmount, true, _upperHint, _lowerHint, _maxFeePercentage);
function withdrawBold(uint _maxFeePercentage, uint _boldAmount ) external override {
_adjustTrove(msg.sender, 0, _boldAmount, true, _maxFeePercentage);
}

// Repay Bold tokens to a Trove: Burn the repaid Bold tokens, and reduce the trove's debt accordingly
function repayBold(uint _boldAmount, address _upperHint, address _lowerHint) external override {
_adjustTrove(msg.sender, 0, _boldAmount, false, _upperHint, _lowerHint, 0);
function repayBold(uint _boldAmount) external override {
_adjustTrove(msg.sender, 0, _boldAmount, false, 0);
}

function adjustTrove(uint _maxFeePercentage, uint _collWithdrawal, uint _boldChange, bool _isDebtIncrease, address _upperHint, address _lowerHint) external payable override {
_adjustTrove(msg.sender, _collWithdrawal, _boldChange, _isDebtIncrease, _upperHint, _lowerHint, _maxFeePercentage);
function adjustTrove(uint _maxFeePercentage, uint _collWithdrawal, uint _boldChange, bool _isDebtIncrease) external payable override {
_adjustTrove(msg.sender, _collWithdrawal, _boldChange, _isDebtIncrease, _maxFeePercentage);
}

function adjustTroveInterestRate(uint _newAnnualInterestRate, address _upperHint, address _lowerHint) external {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn’t this function be permissioned? Either trove owner or the delegate. We may add a TODO and add it when we do delegation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, this one is implicitly permissioned by _requireTroveIsActive, and the function only acts on the Trove owned by msg.sender.

The delegate functionality is TODO.

// TODO: Delegation functionality
_requireValidAnnualInterestRate(_newAnnualInterestRate);
ITroveManager troveManagerCached = troveManager;
_requireTroveisActive(troveManagerCached, msg.sender);

// TODO: apply individual and aggregate pending interest, and take snapshots of current timestamp.
// TODO: determine how applying pending interest should interact / be sequenced with applying pending rewards from redistributions.

troveManagerCached.applyPendingRewards(msg.sender);

sortedTroves.reInsert(msg.sender, _newAnnualInterestRate, _upperHint, _lowerHint);

troveManagerCached.changeAnnualInterestRate(msg.sender, _newAnnualInterestRate);
}

/*
Expand All @@ -237,7 +254,7 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
*
* If both are positive, it will revert.
*/
function _adjustTrove(address _borrower, uint _collWithdrawal, uint _boldChange, bool _isDebtIncrease, address _upperHint, address _lowerHint, uint _maxFeePercentage) internal {
function _adjustTrove(address _borrower, uint _collWithdrawal, uint _boldChange, bool _isDebtIncrease, uint _maxFeePercentage) internal {
ContractsCache memory contractsCache = ContractsCache(troveManager, activePool, boldToken);
LocalVariables_adjustTrove memory vars;

Expand All @@ -255,6 +272,8 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
// Confirm the operation is either a borrower adjusting their own trove, or a pure ETH transfer from the Stability Pool to a trove
assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && msg.value > 0 && _boldChange == 0));

// TODO: apply individual and aggregate pending interest, and take snapshots of current timestamp.

contractsCache.troveManager.applyPendingRewards(_borrower);

// Get the collChange based on whether or not ETH was sent in the transaction
Expand Down Expand Up @@ -288,10 +307,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
(vars.newColl, vars.newDebt) = _updateTroveFromAdjustment(contractsCache.troveManager, _borrower, vars.collChange, vars.isCollIncrease, vars.netDebtChange, _isDebtIncrease);
vars.stake = contractsCache.troveManager.updateStakeAndTotalStakes(_borrower);

// Re-insert trove in to the sorted list
uint newNICR = _getNewNominalICRFromTroveChange(vars.coll, vars.debt, vars.collChange, vars.isCollIncrease, vars.netDebtChange, _isDebtIncrease);
sortedTroves.reInsert(_borrower, newNICR, _upperHint, _lowerHint);

emit TroveUpdated(_borrower, vars.newDebt, vars.newColl, vars.stake, BorrowerOperation.adjustTrove);
emit BoldBorrowingFeePaid(msg.sender, vars.BoldFee);

Expand All @@ -317,6 +332,8 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
uint price = priceFeed.fetchPrice();
_requireNotInRecoveryMode(price);

// TODO: apply individual and aggregate pending interest, and take snapshots of current timestamp.

troveManagerCached.applyPendingRewards(msg.sender);

uint coll = troveManagerCached.getTroveColl(msg.sender);
Expand Down Expand Up @@ -551,28 +568,12 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe
}
}

// --- ICR and TCR getters ---

// Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
function _getNewNominalICRFromTroveChange
(
uint _coll,
uint _debt,
uint _collChange,
bool _isCollIncrease,
uint _debtChange,
bool _isDebtIncrease
)
pure
internal
returns (uint)
{
(uint newColl, uint newDebt) = _getNewTroveAmounts(_coll, _debt, _collChange, _isCollIncrease, _debtChange, _isDebtIncrease);

uint newNICR = LiquityMath._computeNominalCR(newColl, newDebt);
return newNICR;
function _requireValidAnnualInterestRate(uint256 _annualInterestRate) internal pure {
require(_annualInterestRate <= MAX_ANNUAL_INTEREST_RATE, "Interest rate must not be greater than max");
}

// --- ICR and TCR getters ---

// Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
function _getNewICRFromTroveChange
(
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/Dependencies/LiquityBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ contract LiquityBase is BaseMath, ILiquityBase {
uint constant public MIN_NET_DEBT = 1800e18;
// uint constant public MIN_NET_DEBT = 0;

uint256 constant public MAX_ANNUAL_INTEREST_RATE = 1e18; // 100%

uint constant public PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%

uint constant public BORROWING_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
Expand Down
16 changes: 9 additions & 7 deletions contracts/src/Interfaces/IBorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,25 @@ interface IBorrowerOperations is ILiquityBase {
address _boldTokenAddress
) external;

function openTrove(uint _maxFee, uint _boldAmount, address _upperHint, address _lowerHint) external payable;
function openTrove(uint _maxFee, uint _boldAmount, address _upperHint, address _lowerHint, uint256 _annualInterestRate) external payable;

function addColl(address _upperHint, address _lowerHint) external payable;
function addColl() external payable;

function moveETHGainToTrove(address _user, address _upperHint, address _lowerHint) external payable;
function moveETHGainToTrove(address _user) external payable;

function withdrawColl(uint _amount, address _upperHint, address _lowerHint) external;
function withdrawColl(uint _amount) external;

function withdrawBold(uint _maxFee, uint _amount, address _upperHint, address _lowerHint) external;
function withdrawBold(uint _maxFee, uint _amount) external;

function repayBold(uint _amount, address _upperHint, address _lowerHint) external;
function repayBold(uint _amount) external;

function closeTrove() external;

function adjustTrove(uint _maxFee, uint _collWithdrawal, uint _debtChange, bool isDebtIncrease, address _upperHint, address _lowerHint) external payable;
function adjustTrove(uint _maxFee, uint _collWithdrawal, uint _debtChange, bool isDebtIncrease) external payable;

function claimCollateral() external;

function getCompositeDebt(uint _debt) external pure returns (uint);

function adjustTroveInterestRate(uint _newAnnualInterestRate, address _upperHint, address _lowerHint) external;
}
2 changes: 1 addition & 1 deletion contracts/src/Interfaces/IStabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ interface IStabilityPool is ILiquityBase {
* - Leaves their compounded deposit in the Stability Pool
* - Takes new snapshots of accumulators P and S
*/
function withdrawETHGainToTrove(address _upperHint, address _lowerHint) external;
function withdrawETHGainToTrove() external;

/*
* Initial checks:
Expand Down
18 changes: 5 additions & 13 deletions contracts/src/Interfaces/ITroveManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ interface ITroveManager is ILiquityBase {

function liquidate(address _borrower) external;

function liquidateTroves(uint _n) external;

function batchLiquidateTroves(address[] calldata _troveArray) external;

function redeemCollateral(
Expand All @@ -53,8 +51,6 @@ interface ITroveManager is ILiquityBase {

function updateStakeAndTotalStakes(address _borrower) external returns (uint);

function updateTroveRewardSnapshots(address _borrower) external;

function addTroveOwnerToArray(address _borrower) external returns (uint index);

function applyPendingRewards(address _borrower) external;
Expand All @@ -81,14 +77,6 @@ interface ITroveManager is ILiquityBase {

function getRedemptionFeeWithDecay(uint _ETHDrawn) external view returns (uint);

function getBorrowingRate() external view returns (uint);
function getBorrowingRateWithDecay() external view returns (uint);

function getBorrowingFee(uint BoldDebt) external view returns (uint);
function getBorrowingFeeWithDecay(uint _boldDebt) external view returns (uint);

function decayBaseRateFromBorrowing() external;

function getTroveStatus(address _borrower) external view returns (uint);

function getTroveStake(address _borrower) external view returns (uint);
Expand All @@ -97,7 +85,9 @@ interface ITroveManager is ILiquityBase {

function getTroveColl(address _borrower) external view returns (uint);

function setTroveStatus(address _borrower, uint num) external;
function getTroveAnnualInterestRate(address _borrower) external view returns (uint);

function setTrovePropertiesOnOpen(address _borrower, uint256 _coll, uint256 _debt, uint256 _annualInterestRate) external returns (uint256);

function increaseTroveColl(address _borrower, uint _collIncrease) external returns (uint);

Expand All @@ -107,6 +97,8 @@ interface ITroveManager is ILiquityBase {

function decreaseTroveDebt(address _borrower, uint _collDecrease) external returns (uint);

function changeAnnualInterestRate(address _borrower, uint256 _newAnnualInterestRate) external;

function getTCR(uint _price) external view returns (uint);

function checkRecoveryMode(uint _price) external view returns (bool);
Expand Down
6 changes: 4 additions & 2 deletions contracts/src/MultiTroveGetter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ contract MultiTroveGetter {
_troves[idx].coll,
_troves[idx].stake,
/* status */,
/* arrayIndex */
/* arrayIndex */,
/* annualInterestRate */
) = troveManager.Troves(currentTroveowner);
(
_troves[idx].snapshotETH,
Expand Down Expand Up @@ -107,7 +108,8 @@ contract MultiTroveGetter {
_troves[idx].coll,
_troves[idx].stake,
/* status */,
/* arrayIndex */
/* arrayIndex */,
/* annualInterestRate */
) = troveManager.Troves(currentTroveowner);
(
_troves[idx].snapshotETH,
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/PriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ contract PriceFeed is Ownable, CheckContract, BaseMath, IPriceFeed {
* it uses the last good price seen by Liquity.
*
*/
function fetchPrice() external override returns (uint) {
function fetchPrice() external override returns (uint _lastGoodPrice) {
// Get current and previous price data from Chainlink, and current price data from Tellor
ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse();
ChainlinkResponse memory prevChainlinkResponse = _getPrevChainlinkResponse(chainlinkResponse.roundId, chainlinkResponse.decimals);
Expand Down
Loading
Loading