From 4a4fdfdc805bcf735596cc03bc0a1a71a0a03b5e Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Fri, 18 Oct 2024 17:06:51 +0200 Subject: [PATCH] refactor: split GovernorAccessControl into this and GovernorAccessControlEmergency --- src/Governor/GovernorAccessControl.sol | 68 --------------- .../GovernorAccessControlEmergency.sol | 85 +++++++++++++++++++ ...l => GovernorAccessControlEmergency.t.sol} | 8 +- 3 files changed, 89 insertions(+), 72 deletions(-) create mode 100644 src/Governor/GovernorAccessControlEmergency.sol rename test/Governor/{GovernorAccessControl.t.sol => GovernorAccessControlEmergency.t.sol} (97%) diff --git a/src/Governor/GovernorAccessControl.sol b/src/Governor/GovernorAccessControl.sol index f2317c3..e654098 100644 --- a/src/Governor/GovernorAccessControl.sol +++ b/src/Governor/GovernorAccessControl.sol @@ -4,26 +4,12 @@ pragma solidity ^0.8.0; import {SelectorAccessControl} from "../AccessControl/SelectorAccessControl.sol"; import {RevertBytes} from "evk/EVault/shared/lib/RevertBytes.sol"; -import {AmountCap, AmountCapLib} from "evk/EVault/shared/types/AmountCap.sol"; -import {IGovernance} from "evk/EVault/IEVault.sol"; -import "evk/EVault/shared/Constants.sol"; /// @title GovernorAccessControl /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A limited governor contract that allows whitelisted callers to call specific functions on target contracts. contract GovernorAccessControl is SelectorAccessControl { - using AmountCapLib for AmountCap; - - /// @notice Role identifier for emergency borrow LTV adjustments - bytes32 public constant LTV_EMERGENCY_ROLE = keccak256("LTV_EMERGENCY_ROLE"); - - /// @notice Role identifier for emergency vault operations disabling - bytes32 public constant HOOK_EMERGENCY_ROLE = keccak256("HOOK_EMERGENCY_ROLE"); - - /// @notice Role identifier for emergency supply and borrow caps adjustments - bytes32 public constant CAPS_EMERGENCY_ROLE = keccak256("CAPS_EMERGENCY_ROLE"); - /// @notice Error thrown when the message data is invalid. error MsgDataInvalid(); @@ -32,60 +18,6 @@ contract GovernorAccessControl is SelectorAccessControl { /// @param admin The address to be granted the DEFAULT_ADMIN_ROLE. constructor(address evc, address admin) SelectorAccessControl(evc, admin) {} - /// @dev Emergency process allows authorized users to lower borrow LTV without changing liquidation LTV. As with all - /// changes to borrow LTV, this takes effect immediately. The current ramp state for liquidation LTV (if any) is - /// preserved, overriding passed rampDuration parameter if necessary. - function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) - external - virtual - { - IGovernance vault = IGovernance(_targetContract()); - (uint16 currentBorrowLTV, uint16 currentLiquidationLTV,, uint48 currentTargetTimestamp,) = - vault.LTVFull(collateral); - bool isEmergency = borrowLTV < currentBorrowLTV && liquidationLTV == currentLiquidationLTV; - - if (isEmergency && hasRole(LTV_EMERGENCY_ROLE, _msgSender())) { - rampDuration = - currentTargetTimestamp <= block.timestamp ? 0 : uint32(currentTargetTimestamp - block.timestamp); - } else { - _authenticateCaller(); - } - - vault.setLTV(collateral, borrowLTV, liquidationLTV, rampDuration); - } - - /// @dev Emergency process allows authorized users to disable all operations on the vault. - function setHookConfig(address newHookTarget, uint32 newHookedOps) external virtual { - IGovernance vault = IGovernance(_targetContract()); - bool isEmergency = newHookTarget == address(0) && newHookedOps == OP_MAX_VALUE - 1; - - if (!isEmergency || !hasRole(HOOK_EMERGENCY_ROLE, _msgSender())) { - _authenticateCaller(); - } - - vault.setHookConfig(newHookTarget, newHookedOps); - } - - /// @dev Emergency process allows authorized users to lower the caps. - function setCaps(uint16 supplyCap, uint16 borrowCap) external virtual { - IGovernance vault = IGovernance(_targetContract()); - uint256 supplyCapResolved = AmountCap.wrap(supplyCap).resolve(); - uint256 borrowCapResolved = AmountCap.wrap(borrowCap).resolve(); - (uint256 currentSupplyCapResolved, uint256 currentBorrowCapResolved) = vault.caps(); - currentSupplyCapResolved = AmountCap.wrap(uint16(currentSupplyCapResolved)).resolve(); - currentBorrowCapResolved = AmountCap.wrap(uint16(currentBorrowCapResolved)).resolve(); - - bool isEmergency = ( - supplyCapResolved <= currentSupplyCapResolved || borrowCapResolved <= currentBorrowCapResolved - ) && (supplyCapResolved <= currentSupplyCapResolved && borrowCapResolved <= currentBorrowCapResolved); - - if (!isEmergency || !hasRole(CAPS_EMERGENCY_ROLE, _msgSender())) { - _authenticateCaller(); - } - - vault.setCaps(supplyCap, borrowCap); - } - /// @notice Fallback function to forward calls to target contracts. /// @dev This function authenticates the caller, extracts the target contract address from the calldata, and /// forwards the call to the target contract. diff --git a/src/Governor/GovernorAccessControlEmergency.sol b/src/Governor/GovernorAccessControlEmergency.sol new file mode 100644 index 0000000..d4bf166 --- /dev/null +++ b/src/Governor/GovernorAccessControlEmergency.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import {GovernorAccessControl} from "./GovernorAccessControl.sol"; +import {AmountCap, AmountCapLib} from "evk/EVault/shared/types/AmountCap.sol"; +import {IGovernance} from "evk/EVault/IEVault.sol"; +import "evk/EVault/shared/Constants.sol"; + +/// @title GovernorAccessControlEmergency +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice A limited governor contract that allows whitelisted callers to call specific functions on target contracts. +/// It special cases setting LTVs, setting hook config and setting caps to enable custom emergency flows. +contract GovernorAccessControlEmergency is GovernorAccessControl { + using AmountCapLib for AmountCap; + + /// @notice Role identifier for emergency borrow LTV adjustments + bytes32 public constant LTV_EMERGENCY_ROLE = keccak256("LTV_EMERGENCY_ROLE"); + + /// @notice Role identifier for emergency vault operations disabling + bytes32 public constant HOOK_EMERGENCY_ROLE = keccak256("HOOK_EMERGENCY_ROLE"); + + /// @notice Role identifier for emergency supply and borrow caps adjustments + bytes32 public constant CAPS_EMERGENCY_ROLE = keccak256("CAPS_EMERGENCY_ROLE"); + + /// @notice Constructor + /// @param evc The address of the EVC. + /// @param admin The address to be granted the DEFAULT_ADMIN_ROLE. + constructor(address evc, address admin) GovernorAccessControl(evc, admin) {} + + /// @dev Emergency process allows authorized users to lower borrow LTV without changing liquidation LTV. As with all + /// changes to borrow LTV, this takes effect immediately. The current ramp state for liquidation LTV (if any) is + /// preserved, overriding passed rampDuration parameter if necessary. + function setLTV(address collateral, uint16 borrowLTV, uint16 liquidationLTV, uint32 rampDuration) + external + virtual + { + IGovernance vault = IGovernance(_targetContract()); + (uint16 currentBorrowLTV, uint16 currentLiquidationLTV,, uint48 currentTargetTimestamp,) = + vault.LTVFull(collateral); + bool isEmergency = borrowLTV < currentBorrowLTV && liquidationLTV == currentLiquidationLTV; + + if (isEmergency && hasRole(LTV_EMERGENCY_ROLE, _msgSender())) { + rampDuration = + currentTargetTimestamp <= block.timestamp ? 0 : uint32(currentTargetTimestamp - block.timestamp); + } else { + _authenticateCaller(); + } + + vault.setLTV(collateral, borrowLTV, liquidationLTV, rampDuration); + } + + /// @dev Emergency process allows authorized users to disable all operations on the vault. + function setHookConfig(address newHookTarget, uint32 newHookedOps) external virtual { + IGovernance vault = IGovernance(_targetContract()); + bool isEmergency = newHookTarget == address(0) && newHookedOps == OP_MAX_VALUE - 1; + + if (!isEmergency || !hasRole(HOOK_EMERGENCY_ROLE, _msgSender())) { + _authenticateCaller(); + } + + vault.setHookConfig(newHookTarget, newHookedOps); + } + + /// @dev Emergency process allows authorized users to lower the caps. + function setCaps(uint16 supplyCap, uint16 borrowCap) external virtual { + IGovernance vault = IGovernance(_targetContract()); + uint256 supplyCapResolved = AmountCap.wrap(supplyCap).resolve(); + uint256 borrowCapResolved = AmountCap.wrap(borrowCap).resolve(); + (uint256 currentSupplyCapResolved, uint256 currentBorrowCapResolved) = vault.caps(); + currentSupplyCapResolved = AmountCap.wrap(uint16(currentSupplyCapResolved)).resolve(); + currentBorrowCapResolved = AmountCap.wrap(uint16(currentBorrowCapResolved)).resolve(); + + bool isEmergency = ( + supplyCapResolved <= currentSupplyCapResolved || borrowCapResolved <= currentBorrowCapResolved + ) && (supplyCapResolved <= currentSupplyCapResolved && borrowCapResolved <= currentBorrowCapResolved); + + if (!isEmergency || !hasRole(CAPS_EMERGENCY_ROLE, _msgSender())) { + _authenticateCaller(); + } + + vault.setCaps(supplyCap, borrowCap); + } +} diff --git a/test/Governor/GovernorAccessControl.t.sol b/test/Governor/GovernorAccessControlEmergency.t.sol similarity index 97% rename from test/Governor/GovernorAccessControl.t.sol rename to test/Governor/GovernorAccessControlEmergency.t.sol index d21a222..3db2a55 100644 --- a/test/Governor/GovernorAccessControl.t.sol +++ b/test/Governor/GovernorAccessControlEmergency.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {EVaultTestBase} from "evk-test/unit/evault/EVaultTestBase.t.sol"; -import {GovernorAccessControl} from "../../src/Governor/GovernorAccessControl.sol"; +import {GovernorAccessControl, GovernorAccessControlEmergency} from "../../src/Governor/GovernorAccessControlEmergency.sol"; import {IAccessControl} from "openzeppelin-contracts/access/IAccessControl.sol"; import {IGovernance} from "evk/EVault/IEVault.sol"; import "evk/EVault/shared/Constants.sol"; @@ -18,9 +18,9 @@ contract MockTarget { } } -contract GovernorAccessControlTest is EVaultTestBase { +contract GovernorAccessControlEmergencyTest is EVaultTestBase { MockTarget public mockTarget; - GovernorAccessControl public governorAccessControl; + GovernorAccessControlEmergency public governorAccessControl; address public user1; address public user2; address public user1SubAccount; @@ -31,7 +31,7 @@ contract GovernorAccessControlTest is EVaultTestBase { user2 = makeAddr("user2"); user1SubAccount = address(uint160(user1) ^ 100); mockTarget = new MockTarget(); - governorAccessControl = new GovernorAccessControl(address(evc), admin); + governorAccessControl = new GovernorAccessControlEmergency(address(evc), admin); } function test_allowSelector() public {