Skip to content

Commit

Permalink
Merge pull request #20 from euler-xyz/remove-clearLTV
Browse files Browse the repository at this point in the history
Remove clearLTV function
  • Loading branch information
dglowinski authored Jul 30, 2024
2 parents b5fc6f2 + d366c8d commit 5824544
Show file tree
Hide file tree
Showing 9 changed files with 18 additions and 97 deletions.
1 change: 0 additions & 1 deletion docs/specs.md

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/EVault/EVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,6 @@ contract EVault is Dispatch {

function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) public virtual override use(MODULE_GOVERNANCE) {}

function clearLTV(address collateral) public virtual override use(MODULE_GOVERNANCE) {}

function setMaxLiquidationDiscount(uint16 newDiscount) public virtual override use(MODULE_GOVERNANCE) {}

function setLiquidationCoolOffTime(uint16 newCoolOffTime) public virtual override use(MODULE_GOVERNANCE) {}
Expand Down
5 changes: 0 additions & 5 deletions src/EVault/IEVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,6 @@ interface IGovernance {
/// @param rampDuration Ramp duration in seconds
function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) external;

/// @notice Completely clears LTV configuratrion, signalling the collateral is not considered safe to liquidate
/// anymore
/// @param collateral Address of the collateral
function clearLTV(address collateral) external;

/// @notice Set a new maximum liquidation discount
/// @param newDiscount New maximum liquidation discount in 1e4 scale
/// @dev If the discount is zero (the default), the liquidators will not be incentivized to liquidate unhealthy
Expand Down
43 changes: 17 additions & 26 deletions src/EVault/modules/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LT
uint16 liquidationLTV,
uint16 initialLiquidationLTV,
uint48 targetTimestamp,
uint32 rampDuration,
bool initialized
uint32 rampDuration
);

/// @notice Set an interest rate model contract address
/// @param newInterestRateModel Address of the new IRM
event GovSetInterestRateModel(address newInterestRateModel);
Expand Down Expand Up @@ -261,16 +261,19 @@ abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LT
}

/// @inheritdoc IGovernance
/// @dev When the collateral asset is no longer deemed suitable to sustain debt (and not because of code issues, see
/// `clearLTV`), its LTV setting can be set to 0. Setting a zero liquidation LTV also enforces a zero borrowing LTV
/// (`newBorrowLTV <= newLiquidationLTV`). In such cases, the collateral becomes immediately ineffective for new
/// borrows. However, for liquidation purposes, the LTV can be ramped down over a period of time (`rampDuration`).
/// This ramping helps users avoid hard liquidations with maximum discounts and gives them a chance to close their
/// positions in an orderly fashion. The choice of `rampDuration` depends on market conditions assessed by the
/// governor. They may decide to forgo the ramp entirely by setting the duration to zero, presumably in light of
/// extreme market conditions, where ramping would pose a threat to the vault's solvency. In any case, when the
/// liquidation LTV reaches its target of 0, this asset will no longer support the debt, but it will still be
/// possible to liquidate it at a discount and use the proceeds to repay an unhealthy loan.
/// @dev When the collateral asset is no longer deemed suitable to sustain debt, its LTV setting can be set to 0.
/// Setting a zero liquidation LTV also enforces a zero borrowing LTV (`newBorrowLTV <= newLiquidationLTV`).
/// In such cases, the collateral becomes immediately ineffective for new borrows. However, for liquidation
/// purposes, the LTV can be ramped down over a period of time (`rampDuration`). This ramping helps users avoid hard
/// liquidations with maximum discounts and gives them a chance to close their positions in an orderly fashion.
/// The choice of `rampDuration` depends on market conditions assessed by the governor. They may decide to forgo
/// the ramp entirely by setting the duration to zero, presumably in light of extreme market conditions, where
/// ramping would pose a threat to the vault's solvency. In any case, when the liquidation LTV reaches its target
/// of 0, this asset will no longer support the debt, but it will still be possible to liquidate it at a discount
/// and use the proceeds to repay an unhealthy loan.
/// Setting the LTV to zero will not be sufficient if the collateral is found to be unsafe to call liquidation on,
/// either due to a bug or a code upgrade that allows its transfer function to make arbitrary external calls.
/// In such cases, pausing the vault and conducting an orderly wind-down is recommended.
function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration)
public
virtual
Expand All @@ -295,7 +298,7 @@ abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LT

vaultStorage.ltvLookup[collateral] = newLTV;

if (!currentLTV.initialized) vaultStorage.ltvList.push(collateral);
if (!currentLTV.isRecognizedCollateral()) vaultStorage.ltvList.push(collateral);

if (!newLiquidationLTV.isZero()) {
// Ensure that this collateral can be priced by the configured oracle
Expand All @@ -309,22 +312,10 @@ abstract contract GovernanceModule is IGovernance, BalanceUtils, BorrowUtils, LT
newLTV.liquidationLTV.toUint16(),
newLTV.initialLiquidationLTV.toUint16(),
newLTV.targetTimestamp,
newLTV.rampDuration,
!currentLTV.initialized
newLTV.rampDuration
);
}

/// @inheritdoc IGovernance
/// @dev When LTV configuration is cleared, attempt to liquidate the collateral will revert.
/// Clearing should only be executed when the collateral is found to be unsafe to liquidate,
/// because e.g. it does external calls on transfer, which would be a critical security threat.
function clearLTV(address collateral) public virtual nonReentrant governorOnly {
uint16 originalLTV = getLTV(collateral, true).toUint16();
vaultStorage.ltvLookup[collateral].clear();

emit GovSetLTV(collateral, 0, 0, originalLTV, 0, 0, false);
}

/// @inheritdoc IGovernance
function setMaxLiquidationDiscount(uint16 newDiscount) public virtual nonReentrant governorOnly {
// Discount equal 1e4 would cause division by zero error during liquidation
Expand Down
14 changes: 1 addition & 13 deletions src/EVault/shared/types/LTVConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {ConfigAmount} from "./Types.sol";
/// @title LTVConfig
/// @notice This packed struct is used to store LTV configuration of a collateral
struct LTVConfig {
// Packed slot: 2 + 2 + 2 + 6 + 4 + 1 = 17
// Packed slot: 2 + 2 + 2 + 6 + 4 = 16
// The value of borrow LTV for originating positions
ConfigAmount borrowLTV;
// The value of fully converged liquidation LTV
Expand All @@ -18,8 +18,6 @@ struct LTVConfig {
uint48 targetTimestamp;
// The time it takes for the liquidation LTV to converge from the initial value to the fully converged value
uint32 rampDuration;
// A flag indicating the LTV configuration was initialized for the collateral
bool initialized;
}

/// @title LTVConfigLib
Expand Down Expand Up @@ -68,16 +66,6 @@ library LTVConfigLib {
newLTV.initialLiquidationLTV = self.getLTV(true);
newLTV.targetTimestamp = uint48(block.timestamp + rampDuration);
newLTV.rampDuration = rampDuration;
newLTV.initialized = true;
}

// When LTV is cleared, the collateral can't be liquidated, as it's deemed unsafe
function clear(LTVConfig storage self) internal {
self.borrowLTV = ConfigAmount.wrap(0);
self.liquidationLTV = ConfigAmount.wrap(0);
self.initialLiquidationLTV = ConfigAmount.wrap(0);
self.targetTimestamp = 0;
self.rampDuration = 0;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ contract GovernanceModuleHandler is BaseHandler {
assert(true);
}

function clearLTV(uint256 i) external {
address collateral = _getRandomBaseAsset(i);

eTST.clearLTV(collateral);

assert(true);
}

function setInterestFee(uint16 interestFee) external {
eTST.setInterestFee(interestFee);

Expand Down
6 changes: 0 additions & 6 deletions test/unit/evault/modules/Governance/governorOnly.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ contract GovernanceTest_GovernorOnly is EVaultTestBase {
function test_GovernorAdmin() public {
eTST.setFeeReceiver(address(0));
eTST.setLTV(address(0), 0, 0, 0);
eTST.clearLTV(address(0));
eTST.setMaxLiquidationDiscount(0);
eTST.setLiquidationCoolOffTime(0);
eTST.setInterestRateModel(address(0));
Expand All @@ -46,7 +45,6 @@ contract GovernanceTest_GovernorOnly is EVaultTestBase {

evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.setFeeReceiver, address(0)));
evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.setLTV, (address(0), 0, 0, 0)));
evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.clearLTV, address(0)));
evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.setMaxLiquidationDiscount, 0));
evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.setLiquidationCoolOffTime, 0));
evc.call(address(eTST), address(this), 0, abi.encodeCall(eTST.setInterestRateModel, address(0)));
Expand All @@ -68,8 +66,6 @@ contract GovernanceTest_GovernorOnly is EVaultTestBase {
vm.expectRevert(Errors.E_Unauthorized.selector);
eTST.setLTV(address(0), 0, 0, 0);
vm.expectRevert(Errors.E_Unauthorized.selector);
eTST.clearLTV(address(0));
vm.expectRevert(Errors.E_Unauthorized.selector);
eTST.setMaxLiquidationDiscount(0);
vm.expectRevert(Errors.E_Unauthorized.selector);
eTST.setLiquidationCoolOffTime(0);
Expand All @@ -96,8 +92,6 @@ contract GovernanceTest_GovernorOnly is EVaultTestBase {
vm.expectRevert(Errors.E_Unauthorized.selector);
evc.call(address(eTST), subAccount, 0, abi.encodeCall(eTST.setLTV, (address(0), 0, 0, 0)));
vm.expectRevert(Errors.E_Unauthorized.selector);
evc.call(address(eTST), subAccount, 0, abi.encodeCall(eTST.clearLTV, address(0)));
vm.expectRevert(Errors.E_Unauthorized.selector);
evc.call(address(eTST), subAccount, 0, abi.encodeCall(eTST.setMaxLiquidationDiscount, 0));
vm.expectRevert(Errors.E_Unauthorized.selector);
evc.call(address(eTST), subAccount, 0, abi.encodeCall(eTST.setLiquidationCoolOffTime, 0));
Expand Down
30 changes: 0 additions & 30 deletions test/unit/evault/modules/Vault/ltv.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,36 +109,6 @@ contract VaultTest_LTV is EVaultTestBase {
eTST.setLTV(address(eTST2), 1e4 + 1, 1e4 + 1, 0);
}

function test_clearLtv() public {
eTST.setLTV(address(eTST2), 0.5e4, 0.5e4, 0);

startHoax(borrower);
evc.enableCollateral(borrower, address(eTST2));
evc.enableController(borrower, address(eTST));
vm.stopPrank();

// No borrow, liquidation is a no-op
(uint256 maxRepay, uint256 maxYield) = eTST.checkLiquidation(depositor, borrower, address(eTST2));
assertEq(maxRepay, 0);
assertEq(maxYield, 0);

// setting LTV to 0 doesn't change anything yet
eTST.setLTV(address(eTST2), 0, 0, 0);

(maxRepay, maxYield) = eTST.checkLiquidation(depositor, borrower, address(eTST2));
assertEq(maxRepay, 0);
assertEq(maxYield, 0);

// collateral without LTV
vm.expectRevert(Errors.E_BadCollateral.selector);
eTST.checkLiquidation(depositor, borrower, address(eTST));

// same error after clearing LTV
eTST.clearLTV(address(eTST2));
vm.expectRevert(Errors.E_BadCollateral.selector);
eTST.checkLiquidation(depositor, borrower, address(eTST2));
}

function test_ltvList() public {
assertEq(eTST.LTVList().length, 0);

Expand Down
6 changes: 0 additions & 6 deletions test/unit/evault/shared/Reentrancy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,6 @@ contract MockHookTarget is Test, IHookTarget {
uint32(bound(amount2, 0, type(uint32).max))
);

vm.expectRevert(Errors.E_Reentrancy.selector);
eTST.clearLTV(account1);

vm.expectRevert(Errors.E_Reentrancy.selector);
eTST.setInterestRateModel(account1);

Expand Down Expand Up @@ -407,9 +404,6 @@ contract ReentrancyTest is EVaultTestBase {
uint32(bound(amount2, 0, type(uint32).max))
);

vm.expectRevert(Errors.E_Reentrancy.selector);
eTST.clearLTV(account1);

vm.expectRevert(Errors.E_Reentrancy.selector);
eTST.setInterestRateModel(account1);

Expand Down

0 comments on commit 5824544

Please sign in to comment.