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
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
104 changes: 92 additions & 12 deletions contracts/src/ActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ pragma solidity 0.8.18;

import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

import './Interfaces/IActivePool.sol';
import './Interfaces/IBoldToken.sol';
import "./Interfaces/IInterestRouter.sol";
import "./Dependencies/Ownable.sol";
import "./Dependencies/CheckContract.sol";
import './Interfaces/IDefaultPool.sol';
import './Interfaces/IActivePool.sol';

// import "forge-std/console.sol";
//import "forge-std/console2.sol";

/*
* The Active Pool holds the ETH collateral and Bold debt (but not Bold tokens) for all active troves.
Expand All @@ -28,8 +31,31 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
address public troveManagerAddress;
address public stabilityPoolAddress;
address public defaultPoolAddress;

IBoldToken boldToken;

IInterestRouter public interestRouter;

uint256 constant public SECONDS_IN_ONE_YEAR = 31536000; // 60 * 60 * 24 * 365,

uint256 internal ETHBalance; // deposited ether tracker
uint256 internal boldDebt;

// Sum of individual recorded Trove debts. Updated only at individual Trove operations.
// "G" in the spec.
uint256 internal recordedDebtSum;

// Aggregate recorded debt tracker. Updated whenever a Trove's debt is touched AND whenever the aggregate pending interest is minted.
// "D" in the spec.
uint256 public aggRecordedDebt;

/* Sum of individual recorded Trove debts weighted by their respective chosen interest rates.
* Updated at individual Trove operations.
* "S" in the spec.
*/
uint256 public aggWeightedDebtSum;

// Last time at which the aggregate recorded debt and weighted sum were updated
uint256 public lastAggUpdateTime;

// --- Events ---

Expand All @@ -38,21 +64,24 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
event EtherSent(address _to, uint _amount);
event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event ActivePoolBoldDebtUpdated(uint _boldDebt);
event ActivePoolBoldDebtUpdated(uint _recordedDebtSum);
event ActivePoolETHBalanceUpdated(uint _ETHBalance);

constructor(address _ETHAddress) {
checkContract(_ETHAddress);
ETH = IERC20(_ETHAddress);
}


// --- Contract setters ---

function setAddresses(
address _borrowerOperationsAddress,
address _troveManagerAddress,
address _stabilityPoolAddress,
address _defaultPoolAddress
address _defaultPoolAddress,
address _boldTokenAddress,
address _interestRouterAddress
)
external
onlyOwner
Expand All @@ -61,11 +90,15 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
checkContract(_troveManagerAddress);
checkContract(_stabilityPoolAddress);
checkContract(_defaultPoolAddress);
checkContract(_boldTokenAddress);
checkContract(_interestRouterAddress);

borrowerOperationsAddress = _borrowerOperationsAddress;
troveManagerAddress = _troveManagerAddress;
stabilityPoolAddress = _stabilityPoolAddress;
defaultPoolAddress = _defaultPoolAddress;
boldToken = IBoldToken(_boldTokenAddress);
interestRouter = IInterestRouter(_interestRouterAddress);

emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);
emit TroveManagerAddressChanged(_troveManagerAddress);
Expand All @@ -89,8 +122,17 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
return ETHBalance;
}

function getBoldDebt() external view override returns (uint) {
return boldDebt;
function getRecordedDebtSum() external view override returns (uint) {
return recordedDebtSum;
}

function calcPendingAggInterest() public view returns (uint256) {
return aggWeightedDebtSum * (block.timestamp - lastAggUpdateTime) / SECONDS_IN_ONE_YEAR / 1e18;
}

// Returns sum of agg.recorded debt plus agg. pending interest. Excludes pending redist. gains.
function getTotalActiveDebt() public view returns (uint256) {
return aggRecordedDebt + calcPendingAggInterest();
}

// --- Pool functionality ---
Expand Down Expand Up @@ -131,16 +173,54 @@ contract ActivePool is Ownable, CheckContract, IActivePool {
emit ActivePoolETHBalanceUpdated(newETHBalance);
}

function increaseBoldDebt(uint _amount) external override {
function increaseRecordedDebtSum(uint _amount) external override {
_requireCallerIsBOorTroveM();
uint256 newRecordedDebtSum = recordedDebtSum + _amount;
recordedDebtSum = newRecordedDebtSum;
emit ActivePoolBoldDebtUpdated(newRecordedDebtSum);
}

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

recordedDebtSum = newRecordedDebtSum;

emit ActivePoolBoldDebtUpdated(newRecordedDebtSum);
}

function changeAggWeightedDebtSum(uint256 _oldWeightedRecordedTroveDebt, uint256 _newTroveWeightedRecordedTroveDebt) external {
_requireCallerIsBOorTroveM();
boldDebt = boldDebt + _amount;
emit ActivePoolBoldDebtUpdated(boldDebt);
// Do the arithmetic in 2 steps here to avoid overflow from the decrease
uint256 newAggWeightedDebtSum = aggWeightedDebtSum + _newTroveWeightedRecordedTroveDebt; // 1 SLOAD
newAggWeightedDebtSum -= _oldWeightedRecordedTroveDebt;
aggWeightedDebtSum = newAggWeightedDebtSum; // 1 SSTORE
}

function decreaseBoldDebt(uint _amount) external override {
// --- Aggregate interest operations ---

// This function is called inside all state-changing user ops: borrower ops, liquidations, redemptions and SP deposits/withdrawals.
// Some user ops trigger debt changes to Trove(s), in which case _troveDebtChange will be non-zero.
// The aggregate recorded debt is incremented by the aggregate pending interest, plus the net Trove debt change.
// The net Trove debt change consists of the sum of a) any debt issued/repaid and b) any redistribution debt gain applied in the encapsulating operation.
// 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).

_requireCallerIsBOorTroveMorSP();
boldDebt = boldDebt - _amount;
emit ActivePoolBoldDebtUpdated(boldDebt);
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);}

// Do the arithmetic in 2 steps here to avoid overflow from the decrease
uint256 newAggRecordedDebt = aggRecordedDebt + aggInterest + _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.

lastAggUpdateTime = block.timestamp;
}

// --- 'require' functions ---
Expand Down
16 changes: 12 additions & 4 deletions contracts/src/BoldToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ contract BoldToken is CheckContract, IBoldToken {
address public immutable troveManagerAddress;
address public immutable stabilityPoolAddress;
address public immutable borrowerOperationsAddress;
address public immutable activePoolAddress;


// --- Events ---
event TroveManagerAddressChanged(address _troveManagerAddress);
event StabilityPoolAddressChanged(address _newStabilityPoolAddress);
Expand All @@ -66,11 +68,13 @@ contract BoldToken is CheckContract, IBoldToken {
(
address _troveManagerAddress,
address _stabilityPoolAddress,
address _borrowerOperationsAddress
address _borrowerOperationsAddress,
address _activePoolAddress
) {
checkContract(_troveManagerAddress);
checkContract(_stabilityPoolAddress);
checkContract(_borrowerOperationsAddress);
checkContract(_activePoolAddress);

troveManagerAddress = _troveManagerAddress;
emit TroveManagerAddressChanged(_troveManagerAddress);
Expand All @@ -80,6 +84,8 @@ contract BoldToken is CheckContract, IBoldToken {

borrowerOperationsAddress = _borrowerOperationsAddress;
emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);

activePoolAddress = _activePoolAddress;

bytes32 hashedName = keccak256(bytes(_NAME));
bytes32 hashedVersion = keccak256(bytes(_VERSION));
Expand All @@ -95,7 +101,7 @@ contract BoldToken is CheckContract, IBoldToken {
// --- Functions for intra-Liquity calls ---

function mint(address _account, uint256 _amount) external override {
_requireCallerIsBorrowerOperations();
_requireCallerIsBOorAP();
_mint(_account, _amount);
}

Expand Down Expand Up @@ -258,8 +264,10 @@ contract BoldToken is CheckContract, IBoldToken {
);
}

function _requireCallerIsBorrowerOperations() internal view {
require(msg.sender == borrowerOperationsAddress, "BoldToken: Caller is not BorrowerOperations");
function _requireCallerIsBOorAP() internal view {
require(msg.sender == borrowerOperationsAddress ||
msg.sender == activePoolAddress,
"BoldToken: Caller is not BO or AP");
}

function _requireCallerIsBOorTroveMorSP() internal view {
Expand Down
Loading
Loading