Skip to content

Commit

Permalink
Merge branch 'main' into subgraph
Browse files Browse the repository at this point in the history
  • Loading branch information
bpierre committed Sep 4, 2024
2 parents 0079e1e + e9cc36d commit 35c5b06
Show file tree
Hide file tree
Showing 29 changed files with 7,221 additions and 312 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/testnet-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ jobs:

- name: Run deployment tool
working-directory: ./contracts
run: ./deploy liquity-testnet --verify
timeout-minutes: 5
run: ./deploy liquity-testnet --debug --verify
timeout-minutes: 10
env:
DEPLOYER: ${{ secrets.DEPLOYER }}

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ Different PriceFeed contracts are needed for pricing collaterals on different br
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
)`: the Trove owner sets an individual delegate who will have permission to update the interest rate for that Trove in range `[ _minInterestRate, _minInterestRate]`. Removes the Trove from a batch if it was in one.
)`: the Trove owner sets an individual delegate who will have permission to update the interest rate for that Trove in range `[ _minInterestRate, _maxInterestRate]`. Removes the Trove from a batch if it was in one.

- `removeInterestIndividualDelegate(uint256 _troveId):` the Trove owner revokes individual delegate’s permission to change the given Trove’s interest rate.

Expand Down Expand Up @@ -377,7 +377,7 @@ Different PriceFeed contracts are needed for pricing collaterals on different br

### StabilityPool

- `provideToSP(uint256 _amount, bool _doClaim)`: deposit _amount of BOLD to the Stability Pool. It transfers _amount of LUSD from the caller’s address to the Pool, and tops up their BOLD deposit by _amount. `doClaim` determines how the depositor’s existing collateral and BOLD yield gains (if any exist) are treated: if true they’re transferred to the depositor’s address, otherwise the collateral is stashed (added to a balance tracker) and the BOLD gain is added to their deposit.
- `provideToSP(uint256 _amount, bool _doClaim)`: deposit _amount of BOLD to the Stability Pool. It transfers _amount of BOLD from the caller’s address to the Pool, and tops up their BOLD deposit by _amount. `doClaim` determines how the depositor’s existing collateral and BOLD yield gains (if any exist) are treated: if true they’re transferred to the depositor’s address, otherwise the collateral is stashed (added to a balance tracker) and the BOLD gain is added to their deposit.

- `withdrawFromSP(uint256 _amount, bool doClaim)`: withdraws _amount of BOLD from the Stability Pool, up to the value of their remaining deposit. It increases their BOLD balance by _amount. If the user makes a partial withdrawal, their remaining deposit will earn further liquidation and yield gains. `doClaim` determines how the depositor’s existing collateral and BOLD yield gains (if any exist) are treated: if true they’re transferred to the depositor’s address, otherwise the collateral is stashed (added to a balance tracker) and the BOLD gain is added to their deposit.

Expand Down Expand Up @@ -886,7 +886,7 @@ Remove Managers may withdraw collateral or draw new BOLD debt.

### Individual interest delegates

A Trove owner may set an individual delegate at any point after opening.The individual delegate has permission to update the Trove’s interest rate in a range set by the owner, i.e. `[ _minInterestRate, _minInterestRate]`.
A Trove owner may set an individual delegate at any point after opening.The individual delegate has permission to update the Trove’s interest rate in a range set by the owner, i.e. `[ _minInterestRate, _maxInterestRate]`.

A Trove can not be in a managed batch if it has an individual interest delegate.

Expand Down Expand Up @@ -1172,7 +1172,7 @@ https://docs.google.com/spreadsheets/d/1MPVI6edLLbGnqsEo-abijaaLnXle-cJA_vE4CN16
No fix is deemed necessary, since:

- Redemption arbitrage is competitive and profit margins are thin. Chunking redemptions incurs a higher total gas cost and eats into arb profits.
- Redemptions in Liquity v1 (with the same fee formula) have broadly functioned well, and proven effective in restoring the LUSD peg.
- Redemptions in Liquity v1 (with the same fee formula) have broadly functioned well, and proven effective in restoring the BOLD peg.
- The redemption fee spike gain and decay half-life are “best-guess” parameters anyway - there’s little reason to believe that even the intended fee scheme is absolutely optimal.

### 4 - Oracle failure and urgent redemptions with the frozen last good price
Expand Down Expand Up @@ -1350,7 +1350,7 @@ And some additional solutions that may help reduce the chance of bad debt occurr

**Chosen solution**

Solution 7 is chosen. Governance can vote to burn the interest under its control by sending the minted BOLD to a burn address. Although this does not directly clear the bad debt, economically, it should have the same impact - since ultimately, it is the redeemability of _circulating_ BOLD that determines the peg. When an amount equal to the bad debt has been burned, then all circulating BOLD is fully redeemable.
Solution 5 is chosen. Governance can vote to burn the interest under its control by sending the minted BOLD to a burn address. Although this does not directly clear the bad debt, economically, it should have the same impact - since ultimately, it is the redeemability of _circulating_ BOLD that determines the peg. When an amount equal to the bad debt has been burned, then all circulating BOLD is fully redeemable.

See this example:

Expand Down
1 change: 1 addition & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ coverage.json
mochaOutput.json
testMatrix.json
/deployment-context-latest.json
/deployment-manifest.json
40 changes: 40 additions & 0 deletions contracts/addresses/11155111.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"collateralRegistry": "0xa0466829935a47ee765198cfc058414cd97176df",
"boldToken": "0x8b6142b0469bad2a695a25caa2d40778e06ada10",
"hintHelpers": "0x7e665a8c32e69c6384b48654f878a2867748fc76",
"multiTroveGetter": "0x7531c7164e24d26c924e5d028c210dbf9662e7fa",
"branches": [
{
"addressesRegistry": "0xa8b0d10a3f3ba2d761f03c10ee0a9c760601396f",
"activePool": "0xb1ee3803ad36c52d98ad7bd26a7c6f008951fad1",
"borrowerOperations": "0xdf17449500c8e764ed12039bcbbe35fe92af26f2",
"collSurplusPool": "0x64e14c706ab87176d746ea5cf6169584664909df",
"defaultPool": "0x46219e648d51e3c7519f86bc3f0020e96e33d4ae",
"sortedTroves": "0xf65461ffbabf89635e41d69b27e901fa06fff65e",
"stabilityPool": "0x38598385f4117edebe15e7b3034da748bd9ca996",
"troveManager": "0x527fe791cadca7e8512dfdb8f376d89656927f42",
"troveNFT": "0xf6334d66fd9bbd25c8232ca7311029a9470e82b5",
"metadataNFT": "0xd5fd6c77b921f8b2bdd83575b5c8d6cd9b0e94a6",
"priceFeed": "0x866b1f628d0f37dfa394622ed3d4557df2654243",
"gasPool": "0x85245ccf25a87532944bd31ec4fd10d148f9980c",
"interestRouter": "0x48aac152025fe9db7a53527252bca83835094531",
"collToken": "0x75a42d1e31ede2a9e90b1449302efcd606b990aa"
},
{
"addressesRegistry": "0x9493d5d108b7dce38eebff1ca51cad6fb10cbfd6",
"activePool": "0x27ac7b9b871fb22eabf8ee1e60d96cb43c42ba7c",
"borrowerOperations": "0x7162f025c336536473ced83fe039b9a77e31221e",
"collSurplusPool": "0xd229d5fd987be66b0e4e5e5d49441514592b6b0f",
"defaultPool": "0xa8526dd7cac7e73a40f2d6a9c3951755469f3570",
"sortedTroves": "0x22c88b00b58e3ce11e99f049aa582636a1c5fa8b",
"stabilityPool": "0x5d0f599fd0394ff0923fd98809a697d97fd0c76e",
"troveManager": "0x532d5c02b5f46d96ad54576f0b27fca89cd661da",
"troveNFT": "0x8fa3184daa08a960052ee1ddf1e060af421fd2d1",
"metadataNFT": "0xba484bd425af995309c6cdc002c23bdf9d31c0a1",
"priceFeed": "0xafe5f84d89857e87e09d7d860764e7ac6f72d476",
"gasPool": "0xb6a10b5d4ce8903bda4daa4cd6dbbc120bdfb064",
"interestRouter": "0x845ee65bc38b7afc15bd2c91abe4b0f77be17364",
"collToken": "0xb62005e3957e5f64edb144fa0c10146727086dad"
}
]
}
3,387 changes: 3,387 additions & 0 deletions contracts/broadcast/DeployLiquity2.s.sol/11155111/run-1725456290.json

Large diffs are not rendered by default.

3,387 changes: 3,387 additions & 0 deletions contracts/broadcast/DeployLiquity2.s.sol/11155111/run-latest.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ out = "out"
libs = ["lib"]
evm_version = 'shanghai'
ignored_error_codes = [5574] # contract-size
fs_permissions = [{access = "read", path = "./utils/assets/"}]
fs_permissions = [
{ access = "read", path = "./utils/assets/" },
{ access = "write", path = "./deployment-manifest.json" }
]

[invariant]
call_override = false
Expand Down
103 changes: 59 additions & 44 deletions contracts/src/BorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
event ShutDown(uint256 _tcr);
event ShutDownFromOracleFailure(address _oracleAddress);

constructor(IAddressesRegistry _addressesRegistry) AddRemoveManagers(_addressesRegistry) LiquityBase(_addressesRegistry) {
constructor(IAddressesRegistry _addressesRegistry)
AddRemoveManagers(_addressesRegistry)
LiquityBase(_addressesRegistry)
{
// This makes impossible to open a trove with zero withdrawn Bold
assert(MIN_DEBT > 0);

Expand Down Expand Up @@ -713,7 +716,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
// troveChange.newWeightedRecordedDebt = 0;
}

(uint256 price, ) = priceFeed.fetchPrice();
(uint256 price,) = priceFeed.fetchPrice();
uint256 newTCR = _getNewTCRFromTroveChange(troveChange, price);
if (!hasBeenShutDown) _requireNewTCRisAboveCCR(newTCR);

Expand Down Expand Up @@ -813,11 +816,19 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
uint256 _maxUpfrontFee
) external {
_requireIsNotShutDown();
_requireTroveIsActive(troveManager, _troveId);
_requireCallerIsBorrower(_troveId);
_requireValidAnnualInterestRate(_minInterestRate);
_requireValidAnnualInterestRate(_maxInterestRate);
// With the check below, it could only be ==
_requireOrderedRange(_minInterestRate, _maxInterestRate);

interestIndividualDelegateOf[_troveId] =
InterestIndividualDelegate(_delegate, _minInterestRate, _maxInterestRate);
// Can’t have both individual delegation and batch manager
if (interestBatchManagerOf[_troveId] != address(0)) {
// Not needed, implicitly checked in removeFromBatch
//_requireValidAnnualInterestRate(_newAnnualInterestRate);
removeFromBatch(_troveId, _newAnnualInterestRate, _upperHint, _lowerHint, _maxUpfrontFee);
}
}
Expand All @@ -843,7 +854,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
_requireValidAnnualInterestRate(_minInterestRate);
_requireValidAnnualInterestRate(_maxInterestRate);
// With the check below, it could only be ==
if (_minInterestRate >= _maxInterestRate) revert MinGeMax();
_requireOrderedRange(_minInterestRate, _maxInterestRate);
_requireInterestRateInRange(_currentInterestRate, _minInterestRate, _maxInterestRate);
// Not needed, implicitly checked in the condition above:
//_requireValidAnnualInterestRate(_currentInterestRate);
Expand Down Expand Up @@ -1060,7 +1071,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
// Apply upfront fee on premature adjustments
if (
vars.batch.annualInterestRate != _newAnnualInterestRate
&& block.timestamp < vars.trove.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN
&& block.timestamp < vars.trove.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN
) {
vars.trove.entireDebt =
_applyUpfrontFee(vars.trove.entireColl, vars.trove.entireDebt, batchChange, _maxUpfrontFee);
Expand Down Expand Up @@ -1165,7 +1176,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio

uint256 totalColl = getEntireSystemColl();
uint256 totalDebt = getEntireSystemDebt();
(uint256 price, ) = priceFeed.fetchPrice();
(uint256 price,) = priceFeed.fetchPrice();

uint256 TCR = LiquityMath._computeCR(totalColl, totalDebt, price);
if (TCR >= SCR) revert TCRNotBelowSCR();
Expand Down Expand Up @@ -1292,45 +1303,6 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
return batchManager;
}

function _requireInterestRateInDelegateRange(uint256 _troveId, uint256 _annualInterestRate) internal view {
InterestIndividualDelegate memory individualDelegate = interestIndividualDelegateOf[_troveId];
if (individualDelegate.account != address(0)) {
_requireInterestRateInRange(
_annualInterestRate, individualDelegate.minInterestRate, individualDelegate.maxInterestRate
);
}
}

function _requireInterestRateInBatchManagerRange(address _interestBatchManagerAddress, uint256 _annualInterestRate)
internal
view
{
InterestBatchManager memory interestBatchManager = interestBatchManagers[_interestBatchManagerAddress];
_requireInterestRateInRange(
_annualInterestRate, interestBatchManager.minInterestRate, interestBatchManager.maxInterestRate
);
}

function _requireInterestRateInRange(
uint256 _annualInterestRate,
uint256 _minInterestRate,
uint256 _maxInterestRate
) internal pure {
if (_minInterestRate > _annualInterestRate || _annualInterestRate > _maxInterestRate) {
revert InterestNotInRange();
}
}

function _requireInterestRateChangePeriodPassed(
address _interestBatchManagerAddress,
uint256 _lastInterestRateAdjTime
) internal view {
InterestBatchManager memory interestBatchManager = interestBatchManagers[_interestBatchManagerAddress];
if (block.timestamp < _lastInterestRateAdjTime + uint256(interestBatchManager.minInterestRateChangePeriod)) {
revert BatchInterestRateChangePeriodNotPassed();
}
}

function _requireTroveIsOpen(ITroveManager _troveManager, uint256 _troveId) internal view {
ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
if (status != ITroveManager.Status.active && status != ITroveManager.Status.unredeemable) {
Expand Down Expand Up @@ -1467,6 +1439,49 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio
}
}

function _requireOrderedRange(uint256 _minInterestRate, uint256 _maxInterestRate) internal pure {
if (_minInterestRate >= _maxInterestRate) revert MinGeMax();
}

function _requireInterestRateInDelegateRange(uint256 _troveId, uint256 _annualInterestRate) internal view {
InterestIndividualDelegate memory individualDelegate = interestIndividualDelegateOf[_troveId];
if (individualDelegate.account != address(0)) {
_requireInterestRateInRange(
_annualInterestRate, individualDelegate.minInterestRate, individualDelegate.maxInterestRate
);
}
}

function _requireInterestRateInBatchManagerRange(address _interestBatchManagerAddress, uint256 _annualInterestRate)
internal
view
{
InterestBatchManager memory interestBatchManager = interestBatchManagers[_interestBatchManagerAddress];
_requireInterestRateInRange(
_annualInterestRate, interestBatchManager.minInterestRate, interestBatchManager.maxInterestRate
);
}

function _requireInterestRateInRange(
uint256 _annualInterestRate,
uint256 _minInterestRate,
uint256 _maxInterestRate
) internal pure {
if (_minInterestRate > _annualInterestRate || _annualInterestRate > _maxInterestRate) {
revert InterestNotInRange();
}
}

function _requireInterestRateChangePeriodPassed(
address _interestBatchManagerAddress,
uint256 _lastInterestRateAdjTime
) internal view {
InterestBatchManager memory interestBatchManager = interestBatchManagers[_interestBatchManagerAddress];
if (block.timestamp < _lastInterestRateAdjTime + uint256(interestBatchManager.minInterestRateChangePeriod)) {
revert BatchInterestRateChangePeriodNotPassed();
}
}

function _requireValidInterestBatchManager(address _interestBatchManagerAddress) internal view {
if (interestBatchManagers[_interestBatchManagerAddress].maxInterestRate == 0) {
revert InvalidInterestBatchManager();
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/PriceFeeds/CompositePriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ contract CompositePriceFeed is MainnetPriceFeedBase, ICompositePriceFeed {

// If one of Chainlink's responses was invalid in this transaction, disable this PriceFeed and
// return the last good LST-USD price calculated
if (ethUsdOracleDown) {return (_disableFeedAndShutDown(address(ethUsdOracle.aggregator)), true);}
if (lstEthOracleDown) {return (_disableFeedAndShutDown(address(lstEthOracle.aggregator)), true);}
if (ethUsdOracleDown) return (_disableFeedAndShutDown(address(ethUsdOracle.aggregator)), true);
if (lstEthOracleDown) return (_disableFeedAndShutDown(address(lstEthOracle.aggregator)), true);

// Calculate the market LST-USD price: USD_per_LST = USD_per_ETH * ETH_per_LST
uint256 lstUsdMarketPrice = ethUsdPrice * lstEthPrice / 1e18;
Expand Down
6 changes: 3 additions & 3 deletions contracts/src/PriceFeeds/MainnetPriceFeedBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ abstract contract MainnetPriceFeedBase is IPriceFeed, Ownable {
}

// fetchPrice returns:
// - The price
// - The price
// - A bool indicating whether a new oracle failure was detected in the call
function fetchPrice() public returns (uint256, bool) {
if (priceFeedDisabled) {return (lastGoodPrice, false);}
if (priceFeedDisabled) return (lastGoodPrice, false);

return _fetchPrice();
}

// An individual Pricefeed instance implements _fetchPrice according to the data sources it uses. Returns:
// - The price
// - The price
// - A bool indicating whether a new oracle failure was detected in the call
function _fetchPrice() internal virtual returns (uint256, bool) {}

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/PriceFeeds/WETHPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract WETHPriceFeed is MainnetPriceFeedBase, IWETHPriceFeed {
(uint256 ethUsdPrice, bool ethUsdOracleDown) = _getOracleAnswer(ethUsdOracle);

// If the Chainlink response was invalid in this transaction, return the last good ETH-USD price calculated
if (ethUsdOracleDown) {return (_disableFeedAndShutDown(address(ethUsdOracle.aggregator)), true);}
if (ethUsdOracleDown) return (_disableFeedAndShutDown(address(ethUsdOracle.aggregator)), true);

lastGoodPrice = ethUsdPrice;

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/PriceFeeds/WSTETHPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract WSTETHPriceFeed is MainnetPriceFeedBase, IWSTETHPriceFeed {

// If one of Chainlink's responses was invalid in this transaction, disable this PriceFeed and
// return the last good WSTETH-USD price calculated
if (stEthUsdOracleDown) {return (_disableFeedAndShutDown(address(stEthUsdOracle.aggregator)), true);}
if (stEthUsdOracleDown) return (_disableFeedAndShutDown(address(stEthUsdOracle.aggregator)), true);

// Calculate WSTETH-USD price: USD_per_WSTETH = USD_per_STETH * STETH_per_WSTETH
uint256 wstEthUsdPrice = stEthUsdPrice * wstETH.stEthPerToken() / 1e18;
Expand Down
Loading

0 comments on commit 35c5b06

Please sign in to comment.