Skip to content

Commit

Permalink
Merge pull request #4 from Gearbox-protocol/controller-timelock
Browse files Browse the repository at this point in the history
feat: controller timelock moved + fixes
  • Loading branch information
0xmikko authored May 12, 2024
2 parents 2d4505c + 9c1ca0f commit 3b1b6b7
Show file tree
Hide file tree
Showing 19 changed files with 2,891 additions and 15 deletions.
644 changes: 644 additions & 0 deletions contracts/ControllerTimelockV3.sol

Large diffs are not rendered by default.

184 changes: 184 additions & 0 deletions contracts/EmergencyLiquidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {SafeERC20} from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol";

import {ACLNonReentrantTrait} from "@gearbox-protocol/core-v3/contracts/traits/ACLNonReentrantTrait.sol";
import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {ICreditFacadeV3, MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol";

interface IEmergencyLiquidatorExceptions {
/// @dev Thrown when a non-whitelisted account attempts to liquidate an account during pause
error NonWhitelistedLiquidationDuringPauseException();

/// @dev Thrown when a non-whitelisted account attempts to liquidate an account with loss
error NonWhitelistedLiquidationWithLossException();

/// @dev Thrown when liquidation calls contain withdrawals to an address other than emergency liquidator contract
error WithdrawalToExternalAddressException();

/// @dev Thrown when a non-whitelisted address attempts to call an access-restricted function
error CallerNotWhitelistedException();
}

interface IEmergencyLiquidatorEvents {
/// @dev Emitted when a new account is added to / removed from the whitelist
event SetWhitelistedStatus(address indexed account, bool newStatus);

/// @dev Emitted when liquidating during pause is allowed / disallowed
event SetWhitelistedOnlyDuringPause(bool newStatus);

/// @dev Emitted when liquidating with loss is allowed / disallowed
event SetWhitelistedOnlyWithLoss(bool newStatus);
}

contract EmergencyLiquidator is ACLNonReentrantTrait, IEmergencyLiquidatorExceptions, IEmergencyLiquidatorEvents {
using SafeERC20 for IERC20;

/// @dev Thrown when the access-restricted function's caller is not treasury
error CallerNotTreasuryException();

/// @notice Whether the address is a trusted account capable of doing whitelist-only actions
mapping(address => bool) public isWhitelisted;

/// @notice Whether the emergency liquidator currently allows anyone to liquidate during pause
/// or only whitelisted addresses
bool public whitelistedOnlyDuringPause;

/// @notice Whether the emergency liquidator currently allows anyone to liquidate with loss or only
/// whitelisted addresses
bool public whitelistedOnlyWithLoss;

constructor(address _addressProvider) ACLNonReentrantTrait(_addressProvider) {}

modifier whitelistedOnly() {
if (!isWhitelisted[msg.sender]) revert CallerNotWhitelistedException();
_;
}

/// @dev Checks that the liquidation satisfies certain criteria if the account is not whitelisted, reverts if not:
/// - If the contract is paused, checks whether liquidations during pause are available to non-whitelisted accounts
/// - If the liquidation is lossy (detected by Credit Facade internal loss counter increasing), checks whether lossy liquidations are available
/// to non-whitelisted account
modifier checkWhitelistedActions(address creditFacade) {
if (isWhitelisted[msg.sender]) {
_;
} else {
if (Pausable(creditFacade).paused() && whitelistedOnlyDuringPause) {
revert NonWhitelistedLiquidationDuringPauseException();
}

uint128 cumulativeLossBefore;

if (whitelistedOnlyWithLoss) {
cumulativeLossBefore = _cumulativeLoss(creditFacade);
}

_;

if (whitelistedOnlyWithLoss) {
uint128 cumulativeLossAfter = _cumulativeLoss(creditFacade);

if (cumulativeLossAfter > cumulativeLossBefore) {
revert NonWhitelistedLiquidationWithLossException();
}
}
}
}

/// @dev Checks that all withdrawals are sent to this contract, reverts if not
modifier checkWithdrawalDestinations(address creditFacade, MultiCall[] calldata calls) {
_checkWithdrawalsDestination(creditFacade, calls);
_;
}

/// @notice Liquidates a credit account, while checking restrictions on liquidations during pause (if any)
function liquidateCreditAccount(address creditFacade, address creditAccount, MultiCall[] calldata calls)
external
checkWithdrawalDestinations(creditFacade, calls)
checkWhitelistedActions(creditFacade)
{
ICreditFacadeV3(creditFacade).liquidateCreditAccount(creditAccount, address(this), calls);
}

/// @notice Liquidates a credit account with max underlying approval, allowing to buy collateral with DAO funds
/// @dev Can be exploited by account owners when open to everyone, and thus is only allowed for whitelisted addresses
function liquidateCreditAccountWithApproval(address creditFacade, address creditAccount, MultiCall[] calldata calls)
external
checkWithdrawalDestinations(creditFacade, calls)
whitelistedOnly
{
address creditManager = ICreditFacadeV3(creditFacade).creditManager();
address underlying = ICreditManagerV3(creditManager).underlying();

IERC20(underlying).forceApprove(creditManager, type(uint256).max);
ICreditFacadeV3(creditFacade).liquidateCreditAccount(creditAccount, address(this), calls);
IERC20(underlying).forceApprove(creditManager, 1);
}

/// @dev Checks that the provided calldata has all withdrawals sent to this contract
function _checkWithdrawalsDestination(address creditFacade, MultiCall[] calldata calls) internal view {
uint256 len = calls.length;

for (uint256 i = 0; i < len;) {
if (
calls[i].target == creditFacade
&& bytes4(calls[i].callData) == ICreditFacadeV3Multicall.withdrawCollateral.selector
) {
(,, address to) = abi.decode(calls[i].callData[4:], (address, uint256, address));

if (to != address(this)) revert WithdrawalToExternalAddressException();
}

unchecked {
++i;
}
}
}

/// @dev Retrieves cumulative loss for a credit facade
function _cumulativeLoss(address creditFacade) internal view returns (uint128 cumulativeLoss) {
(cumulativeLoss,) = ICreditFacadeV3(creditFacade).lossParams();
}

/// @notice Sends funds accumulated from liquidations to a specified address
function withdrawFunds(address token, address to) external configuratorOnly {
uint256 bal = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(to, bal);
}

/// @notice Sets the status of an account as whitelisted
function setWhitelistedAccount(address account, bool newStatus) external configuratorOnly {
bool whitelistedStatus = isWhitelisted[account];

if (newStatus != whitelistedStatus) {
isWhitelisted[account] = newStatus;
emit SetWhitelistedStatus(account, newStatus);
}
}

/// @notice Sets whether liquidations during pause are only allowed to whitelisted addresses
function setWhitelistedOnlyDuringPause(bool newStatus) external configuratorOnly {
bool currentStatus = whitelistedOnlyDuringPause;

if (newStatus != currentStatus) {
whitelistedOnlyDuringPause = newStatus;
emit SetWhitelistedOnlyDuringPause(newStatus);
}
}

/// @notice Sets whether liquidations with loss are only allowed to whitelisted addresses
function setWhitelistedOnlyWithLoss(bool newStatus) external configuratorOnly {
bool currentStatus = whitelistedOnlyWithLoss;

if (newStatus != currentStatus) {
whitelistedOnlyWithLoss = newStatus;
emit SetWhitelistedOnlyWithLoss(newStatus);
}
}
}
97 changes: 97 additions & 0 deletions contracts/PolicyManagerV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;

import {ACLNonReentrantTrait} from "@gearbox-protocol/core-v3/contracts/traits/ACLNonReentrantTrait.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";

struct Policy {
bool enabled;
address admin;
uint40 delay;
bool checkInterval;
bool checkSet;
uint256 intervalMinValue;
uint256 intervalMaxValue;
uint256[] setValues;
}

/// @title Policy manager V3
/// @dev A contract for managing bounds and conditions for mission-critical protocol params
abstract contract PolicyManagerV3 is ACLNonReentrantTrait {
/// @dev Mapping from group-derived key to policy
mapping(string => Policy) internal _policies;

/// @notice Emitted when new policy is set
event SetPolicy(string indexed policyID, bool enabled);

constructor(address _acl) ACLNonReentrantTrait(_acl) {}

/// @notice Sets the params for a new or existing policy, using policy UID as key
/// @param policyID A unique identifier for a policy, generally, should be the signature of a method which uses the policy.
/// Can also in some cases need additional parameters to be concatenated
/// @param policyParams Policy parameters
function setPolicy(string calldata policyID, Policy memory policyParams)
external
configuratorOnly // U:[PM-1]
{
policyParams.enabled = true; // U:[PM-1]
_policies[policyID] = policyParams; // U:[PM-1]
emit SetPolicy({policyID: policyID, enabled: true}); // U:[PM-1]
}

/// @notice Disables the policy which makes all requested checks for the passed policy hash to auto-fail
/// @param policyID A unique identifier for a policy
function disablePolicy(string calldata policyID)
public
configuratorOnly // U:[PM-2]
{
_policies[policyID].enabled = false; // U:[PM-2]
emit SetPolicy({policyID: policyID, enabled: false}); // U:[PM-2]
}

/// @notice Retrieves policy from policy UID
function getPolicy(string calldata policyID) external view returns (Policy memory) {
return _policies[policyID]; // U:[PM-1]
}

/// @dev Returns policy transaction delay, with policy retrieved based on contract and parameter name
function _getPolicyDelay(string memory policyID) internal view returns (uint256) {
return _policies[policyID].delay;
}

/// @dev Performs parameter checks, with policy retrieved based on policy UID
function _checkPolicy(string memory policyID, uint256 newValue) internal returns (bool) {
Policy storage policy = _policies[policyID];

if (!policy.enabled) return false; // U:[PM-2]

if (policy.admin != msg.sender) return false; // U: [PM-5]

if (policy.checkInterval) {
if (newValue < policy.intervalMinValue || newValue > policy.intervalMaxValue) return false; // U: [PM-3]
}

if (policy.checkSet) {
if (!_isIn(policy.setValues, newValue)) return false; // U: [PM-4]
}

return true;
}

/// @dev Returns whether the value is an element of `arr`
function _isIn(uint256[] memory arr, uint256 value) internal pure returns (bool) {
uint256 len = arr.length;

for (uint256 i = 0; i < len;) {
if (value == arr[i]) return true;

unchecked {
++i;
}
}

return false;
}
}
2 changes: 1 addition & 1 deletion contracts/factories/AdapterFactoryV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pragma solidity ^0.8.17;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

interface IAdapterDeployer {
function deploy(address creditManager, address target, bytes calldata specificParams) external returns (address);
Expand Down
2 changes: 1 addition & 1 deletion contracts/factories/CreditFactoryV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pragma solidity ^0.8.17;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {CreditManagerV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditManagerV3.sol";
import {IBytecodeRepository} from "./IBytecodeRepository.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/factories/IBytecodeRepository.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

interface IBytecodeRepositoryEvents {}

Expand Down
2 changes: 1 addition & 1 deletion contracts/factories/InterestModelFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.17;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

contract InterestModelFactory is IVersion {
Expand Down
2 changes: 1 addition & 1 deletion contracts/factories/MarketConfiguratorFactoryV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {MarketConfigurator} from "./MarketConfigurator.sol";
import {ACL} from "../primitives/ACL.sol";
import {ContractsRegister} from "../primitives/ContractsRegister.sol";
import {IAddressProviderV3} from "../interfaces/IAddressProviderV3.sol";
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

import {IBytecodeRepository} from "./IBytecodeRepository.sol";
import {AP_MARKET_CONFIGURATOR} from "./ContractLiterals.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/factories/PoolFactoryV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pragma solidity ^0.8.17;

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
import {AbstractFactory} from "./AbstractFactory.sol";
import {AP_POOL, AP_POOL_QUOTA_KEEPER, AP_POOL_RATE_KEEPER, AP_DEGEN_NFT} from "./ContractLiterals.sol";
import {MarketConfigurator} from "./MarketConfigurator.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IACL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

interface IACLExceptions {
/// @dev Thrown when attempting to delete an address from a set that is not a pausable admin
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IAddressProviderV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

uint256 constant NO_VERSION_CONTROL = 0;

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IContractsRegister.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/IVersion.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

/// @title Contracts register interface
interface IContractsRegister is IVersion {
Expand Down
Loading

0 comments on commit 3b1b6b7

Please sign in to comment.