From aea60f480468dd021f34d3097350b40b091ee060 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Thu, 23 Nov 2023 22:03:56 -0600 Subject: [PATCH 1/2] Fix VolatilityOracle block.timestamp casting --- core/src/VolatilityOracle.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/VolatilityOracle.sol b/core/src/VolatilityOracle.sol index 9870a9a..b9338d8 100644 --- a/core/src/VolatilityOracle.sol +++ b/core/src/VolatilityOracle.sol @@ -41,7 +41,7 @@ contract VolatilityOracle { if (lastWrites[pool].time == 0) { feeGrowthGlobals[pool][0] = _getFeeGrowthGlobalsNow(pool); - lastWrites[pool] = LastWrite(0, uint32(block.timestamp), IV_COLD_START, IV_COLD_START); + lastWrites[pool] = LastWrite(0, uint40(block.timestamp), IV_COLD_START, IV_COLD_START); } } @@ -67,7 +67,7 @@ contract VolatilityOracle { // Bring `lastWrite` forward so it's essentially "currentWrite" lastWrite.index = uint8((lastWrite.index + 1) % FEE_GROWTH_ARRAY_LENGTH); - lastWrite.time = uint32(block.timestamp); + lastWrite.time = uint40(block.timestamp); lastWrite.oldIV = lastWrite.newIV; // lastWrite.newIV is updated below, iff feeGrowthGlobals samples are ≈`FEE_GROWTH_AVG_WINDOW` hours apart From 65e345cb4f2a0d2b0c9115d085fad75ad5159131 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Thu, 23 Nov 2023 22:10:19 -0600 Subject: [PATCH 2/2] Add Borrower.unwarn method (#225) --- core/src/Borrower.sol | 54 +++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/core/src/Borrower.sol b/core/src/Borrower.sol index 1e0c769..6baa3da 100644 --- a/core/src/Borrower.sol +++ b/core/src/Borrower.sol @@ -61,6 +61,13 @@ contract Borrower is IUniswapV3MintCallback { */ event Warn(); + /** + * @notice The opposite of `Warn`. Fortuitous price movements and/or direct `Lender.repay` can bring the + * account back to health, but the warning won't be cleared until `unwarn` or `modify` is called. This is + * emitted in the `unwarn` case. + */ + event Unwarn(); + /** * @notice Emitted when the account gets `liquidate`d * @param repay0 The amount of `TOKEN0` that was repaid @@ -156,22 +163,34 @@ contract Borrower is IUniswapV3MintCallback { uint256 slot0_ = slot0; // Essentially `slot0.state == State.Ready && slot0.unleashLiquidationTime == 0` require(slot0_ & (SLOT0_MASK_STATE | SLOT0_MASK_UNLEASH) == 0); - - { - // Fetch prices from oracle - (Prices memory prices, ) = getPrices(oracleSeed); - // Tally assets without actually withdrawing Uniswap positions - Assets memory assets = _getAssets(slot0_, prices, false); - // Fetch liabilities from lenders - (uint256 liabilities0, uint256 liabilities1) = _getLiabilities(); - // Ensure only unhealthy accounts get warned - require(!BalanceSheet.isHealthy(prices, assets, liabilities0, liabilities1), "Aloe: healthy"); - } + // Ensure only unhealthy accounts get warned + require(!_isHealthy(slot0_, oracleSeed), "Aloe: healthy"); slot0 = slot0_ | ((block.timestamp + LIQUIDATION_GRACE_PERIOD) << 208); emit Warn(); - SafeTransferLib.safeTransferETH(msg.sender, address(this).balance >> 3); + SafeTransferLib.safeTransferETH(msg.sender, address(this).balance / 5); + } + + /** + * @notice Clears the warning state from a healthy borrower. NOTE: There is no guarantee that this + * will be called. Users are encouraged to clear the warning state via `modify` as soon as possible. + * @param oracleSeed The indices of `UNISWAP_POOL.observations` where we start our search for + * the 30-minute-old (lowest 16 bits) and 60-minute-old (next 16 bits) observations when getting + * TWAPs. If any of the highest 8 bits are set, we fallback to onchain binary search. + */ + function unwarn(uint40 oracleSeed) external { + uint256 slot0_ = slot0; + // Essentially `slot0.state == State.Ready && slot0.auctionTime > 0` + require(slot0_ & SLOT0_MASK_STATE == 0 && slot0_ & SLOT0_MASK_UNLEASH > 0); + // Ensure only healthy accounts get unwarned + require(_isHealthy(slot0_, oracleSeed), "Aloe: unhealthy"); + + // Stop auction + slot0 = (slot0_ & SLOT0_MASK_POSITIONS) | SLOT0_DIRT; + emit Unwarn(); + + SafeTransferLib.safeTransferETH(msg.sender, address(this).balance / 4); } /** @@ -490,6 +509,17 @@ contract Borrower is IUniswapV3MintCallback { ); } + function _isHealthy(uint256 slot0_, uint40 oracleSeed) private returns (bool) { + // Fetch prices from oracle + (Prices memory prices, ) = getPrices(oracleSeed); + // Tally assets without actually withdrawing Uniswap positions + Assets memory assets = _getAssets(slot0_, prices, false); + // Fetch liabilities from lenders + (uint256 liabilities0, uint256 liabilities1) = _getLiabilities(); + + return BalanceSheet.isHealthy(prices, assets, liabilities0, liabilities1); + } + function _getAssets(uint256 slot0_, Prices memory prices, bool withdraw) private returns (Assets memory assets) { assets.fixed0 = TOKEN0.balanceOf(address(this)); assets.fixed1 = TOKEN1.balanceOf(address(this));