// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;
import {Module} from "src/Kernel.sol";
/// @title Olympus Clearinghouse Registry
/// @notice Olympus Clearinghouse Registry (Module) Contract
/// @dev The Olympus Clearinghouse Registry Module tracks the lending facilities that the Olympus
/// protocol deploys to satisfy the Cooler Loan demand. This allows for a single-source of truth
/// for reporting purposes around the total Treasury holdings as well as its projected receivables.
abstract contract CHREGv1 is Module {
// ========= ERRORS ========= //
error CHREG_InvalidConstructor();
error CHREG_NotActivated(address clearinghouse_);
error CHREG_AlreadyActivated(address clearinghouse_);
// ========= EVENTS ========= //
/// @notice Logs whenever a Clearinghouse is activated.
/// If it is the first time, it is also added to the registry.
event ClearinghouseActivated(address indexed clearinghouse);
/// @notice Logs whenever an active Clearinghouse is deactivated.
event ClearinghouseDeactivated(address indexed clearinghouse);
// ========= STATE ========= //
/// @notice Count of active and historical clearinghouses.
/// @dev These are useless variables in contracts, but useful for any frontends
/// or off-chain requests where the array is not easily accessible.
uint256 public activeCount;
uint256 public registryCount;
/// @notice Tracks the addresses of all the active Clearinghouses.
address[] public active;
/// @notice Historical record of all the Clearinghouse addresses.
address[] public registry;
// ========= FUNCTIONS ========= //
/// @notice Adds a Clearinghouse to the registry.
/// Only callable by permissioned policies.
///
/// @param clearinghouse_ The address of the clearinghouse.
function activateClearinghouse(address clearinghouse_) external virtual;
/// @notice Deactivates a clearinghouse from the registry.
/// Only callable by permissioned policies.
///
/// @param clearinghouse_ The address of the clearinghouse.
function deactivateClearinghouse(address clearinghouse_) external virtual;
}
// SPDX-License-Identifier: BSD
pragma solidity ^0.8.4;
/// @title Clone
/// @author zefram.eth
/// @notice Provides helper functions for reading immutable args from calldata
contract Clone {
/// @notice Reads an immutable arg with type address
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgAddress(uint256 argOffset)
internal
pure
returns (address arg)
{
uint256 offset = _getImmutableArgsOffset();
assembly {
arg := shr(0x60, calldataload(add(offset, argOffset)))
}
}
/// @notice Reads an immutable arg with type uint256
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint256(uint256 argOffset)
internal
pure
returns (uint256 arg)
{
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := calldataload(add(offset, argOffset))
}
}
/// @notice Reads an immutable arg with type uint64
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint64(uint256 argOffset)
internal
pure
returns (uint64 arg)
{
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := shr(0xc0, calldataload(add(offset, argOffset)))
}
}
/// @notice Reads an immutable arg with type uint8
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) {
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := shr(0xf8, calldataload(add(offset, argOffset)))
}
}
/// @return offset The offset of the packed immutable args in calldata
function _getImmutableArgsOffset() internal pure returns (uint256 offset) {
// solhint-disable-next-line no-inline-assembly
assembly {
offset := sub(
calldatasize(),
add(shr(240, calldataload(sub(calldatasize(), 2))), 2)
)
}
}
}
// SPDX-License-Identifier: BSD
pragma solidity ^0.8.4;
/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
error CreateFail();
/// @notice Creates a clone proxy of the implementation contract, with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function clone(address implementation, bytes memory data)
internal
returns (address instance)
{
// unrealistic for memory ptr or data length to exceed 256 bits
unchecked {
uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call
uint256 creationSize = 0x43 + extraLength;
uint256 runSize = creationSize - 11;
uint256 dataPtr;
uint256 ptr;
// solhint-disable-next-line no-inline-assembly
assembly {
ptr := mload(0x40)
// -------------------------------------------------------------------------------------------------------------
// CREATION (11 bytes)
// -------------------------------------------------------------------------------------------------------------
// 3d | RETURNDATASIZE | 0 | –
// 61 runtime | PUSH2 runtime (r) | r 0 | –
mstore(
ptr,
0x3d61000000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x02), shl(240, runSize)) // size of the contract running bytecode (16 bits)
// creation size = 0b
// 80 | DUP1 | r r 0 | –
// 60 creation | PUSH1 creation (c) | c r r 0 | –
// 3d | RETURNDATASIZE | 0 c r r 0 | –
// 39 | CODECOPY | r 0 | [0-2d]: runtime code
// 81 | DUP2 | 0 c 0 | [0-2d]: runtime code
// f3 | RETURN | 0 | [0-2d]: runtime code
mstore(
add(ptr, 0x04),
0x80600b3d3981f300000000000000000000000000000000000000000000000000
)
// -------------------------------------------------------------------------------------------------------------
// RUNTIME
// -------------------------------------------------------------------------------------------------------------
// 36 | CALLDATASIZE | cds | –
// 3d | RETURNDATASIZE | 0 cds | –
// 3d | RETURNDATASIZE | 0 0 cds | –
// 37 | CALLDATACOPY | – | [0, cds] = calldata
// 61 | PUSH2 extra | extra | [0, cds] = calldata
mstore(
add(ptr, 0x0b),
0x363d3d3761000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x10), shl(240, extraLength))
// 60 0x38 | PUSH1 0x38 | 0x38 extra | [0, cds] = calldata // 0x38 (56) is runtime size - data
// 36 | CALLDATASIZE | cds 0x38 extra | [0, cds] = calldata
// 39 | CODECOPY | _ | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata
// 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata
// 61 extra | PUSH2 extra | extra cds 0 0 0 | [0, cds] = calldata
mstore(
add(ptr, 0x12),
0x603836393d3d3d36610000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x1b), shl(240, extraLength))
// 01 | ADD | cds+extra 0 0 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata
// 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata
mstore(
add(ptr, 0x1d),
0x013d730000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x20), shl(0x60, implementation))
// 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata
// f4 | DELEGATECALL | success 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata
// 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata
// 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata
// 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds)
// 90 | SWAP1 | 0 success | [0, rds] = return data
// 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data
// 91 | SWAP2 | success 0 rds | [0, rds] = return data
// 60 0x36 | PUSH1 0x36 | 0x36 sucess 0 rds | [0, rds] = return data
// 57 | JUMPI | 0 rds | [0, rds] = return data
// fd | REVERT | – | [0, rds] = return data
// 5b | JUMPDEST | 0 rds | [0, rds] = return data
// f3 | RETURN | – | [0, rds] = return data
mstore(
add(ptr, 0x34),
0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000
)
}
// -------------------------------------------------------------------------------------------------------------
// APPENDED DATA (Accessible from extcodecopy)
// (but also send as appended data to the delegatecall)
// -------------------------------------------------------------------------------------------------------------
extraLength -= 2;
uint256 counter = extraLength;
uint256 copyPtr = ptr + 0x43;
// solhint-disable-next-line no-inline-assembly
assembly {
dataPtr := add(data, 32)
}
for (; counter >= 32; counter -= 32) {
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, mload(dataPtr))
}
copyPtr += 32;
dataPtr += 32;
}
uint256 mask = ~(256**(32 - counter) - 1);
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, and(mload(dataPtr), mask))
}
copyPtr += counter;
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, shl(240, extraLength))
}
// solhint-disable-next-line no-inline-assembly
assembly {
instance := create(0, ptr, creationSize)
}
if (instance == address(0)) {
revert CreateFail();
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {Clone} from "clones/Clone.sol";
import {CoolerFactory} from "./CoolerFactory.sol";
import {CoolerCallback} from "./CoolerCallback.sol";
// Function sig taken from gOHM contract
interface IDelegate {
function delegate(address to_) external;
}
/// @title Cooler Loans.
/// @notice A Cooler is a smart contract escrow that facilitates fixed-duration, peer-to-peer
/// loans for a user-defined debt-collateral pair.
/// @dev This contract uses Clones (https://github.com/wighawag/clones-with-immutable-args)
/// to save gas on deployment.
contract Cooler is Clone {
using SafeTransferLib for ERC20;
// --- ERRORS ----------------------------------------------------
error OnlyApproved();
error Deactivated();
error Default();
error NotExpired();
error NotCoolerCallback();
// --- DATA STRUCTURES -------------------------------------------
/// @notice A loan begins with a borrow request.
struct Request {
uint256 amount; // Amount to be borrowed.
uint256 interest; // Annualized percentage to be paid as interest.
uint256 loanToCollateral; // Requested loan-to-collateral ratio.
uint256 duration; // Time to repay the loan before it defaults.
bool active; // Any lender can clear an active loan request.
address requester; // The address that created the request.
}
/// @notice A request is converted to a loan when a lender clears it.
struct Loan {
Request request; // Loan terms specified in the request.
uint256 principal; // Amount of principal debt owed to the lender.
uint256 interestDue; // Interest owed to the lender.
uint256 collateral; // Amount of collateral pledged.
uint256 expiry; // Time when the loan defaults.
address lender; // Lender's address.
address recipient; // Recipient of repayments.
bool callback; // If this is true, the lender must inherit CoolerCallback.
}
// --- IMMUTABLES ------------------------------------------------
// This makes the code look prettier.
uint256 private constant DECIMALS_INTEREST = 1e18;
/// @notice This address owns the collateral in escrow.
function owner() public pure returns (address _owner) {
return _getArgAddress(0x0);
}
/// @notice This token is borrowed against.
function collateral() public pure returns (ERC20 _collateral) {
return ERC20(_getArgAddress(0x14));
}
/// @notice This token is lent.
function debt() public pure returns (ERC20 _debt) {
return ERC20(_getArgAddress(0x28));
}
/// @notice This contract created the Cooler
function factory() public pure returns (CoolerFactory _factory) {
return CoolerFactory(_getArgAddress(0x3c));
}
// --- STATE VARIABLES -------------------------------------------
/// @notice Arrays stores all the loan requests.
Request[] public requests;
/// @notice Arrays stores all the granted loans.
Loan[] public loans;
/// @notice Facilitates transfer of lender ownership to new addresses
mapping(uint256 => address) public approvals;
// --- BORROWER --------------------------------------------------
/// @notice Request a loan with given parameters.
/// Collateral is taken at time of request.
/// @param amount_ of debt tokens to borrow.
/// @param interest_ to pay (annualized % of 'amount_'). Expressed in DECIMALS_INTEREST.
/// @param loanToCollateral_ debt tokens per collateral token pledged. Expressed in 10**collateral().decimals().
/// @param duration_ of loan tenure in seconds.
/// @return reqID of the created request. Equivalent to the index of request in requests[].
function requestLoan(
uint256 amount_,
uint256 interest_,
uint256 loanToCollateral_,
uint256 duration_
) external returns (uint256 reqID) {
reqID = requests.length;
requests.push(
Request({
amount: amount_,
interest: interest_,
loanToCollateral: loanToCollateral_,
duration: duration_,
active: true,
requester: msg.sender
})
);
// The collateral is taken upfront. Will be escrowed
// until the loan is repaid or defaulted.
collateral().safeTransferFrom(
msg.sender,
address(this),
collateralFor(amount_, loanToCollateral_)
);
// Log the event.
factory().logRequestLoan(reqID);
}
/// @notice Cancel a loan request and get the collateral back.
/// @param reqID_ index of request in requests[].
function rescindRequest(uint256 reqID_) external {
if (msg.sender != owner()) revert OnlyApproved();
Request storage req = requests[reqID_];
if (!req.active) revert Deactivated();
// Update storage and send collateral back to the owner.
req.active = false;
collateral().safeTransfer(owner(), collateralFor(req.amount, req.loanToCollateral));
// Log the event.
factory().logRescindRequest(reqID_);
}
/// @notice Repay a loan to get the collateral back.
/// @dev Despite a malicious lender could reenter with the callback, the
/// usage of `msg.sender` prevents any economical benefit to the
/// attacker, since they would be repaying the loan themselves.
/// @param loanID_ index of loan in loans[].
/// @param repayment_ debt tokens to be repaid.
/// @return collateral given back to the borrower.
function repayLoan(uint256 loanID_, uint256 repayment_) external returns (uint256) {
Loan memory loan = loans[loanID_];
if (block.timestamp > loan.expiry) revert Default();
// Cap the repayment to the total debt of the loan
uint256 totalDebt = loan.principal + loan.interestDue;
if (repayment_ > totalDebt) repayment_ = totalDebt;
// Need to repay interest first, then any extra goes to paying down principal.
uint256 interestPaid;
uint256 remainder;
if (repayment_ >= loan.interestDue) {
remainder = repayment_ - loan.interestDue;
interestPaid = loan.interestDue;
loan.interestDue = 0;
} else {
loan.interestDue -= repayment_;
interestPaid = repayment_;
}
// We pay back only if user has paid back principal. This can be 0.
uint256 decollateralized;
if (remainder > 0) {
decollateralized = (loan.collateral * remainder) / loan.principal;
loan.principal -= remainder;
loan.collateral -= decollateralized;
}
// Save updated loan info in storage.
loans[loanID_] = loan;
// Transfer repaid debt back to the lender and collateral back to the owner if applicable
debt().safeTransferFrom(msg.sender, loan.recipient, repayment_);
if (decollateralized > 0) collateral().safeTransfer(owner(), decollateralized);
// Log the event.
factory().logRepayLoan(loanID_, repayment_);
// If necessary, trigger lender callback.
if (loan.callback) {
CoolerCallback(loan.lender).onRepay(loanID_, remainder, interestPaid);
}
return decollateralized;
}
/// @notice Delegate voting power on collateral.
/// @param to_ address to delegate.
function delegateVoting(address to_) external {
if (msg.sender != owner()) revert OnlyApproved();
IDelegate(address(collateral())).delegate(to_);
}
// --- LENDER ----------------------------------------------------
/// @notice Fill a requested loan as a lender.
/// @param reqID_ index of request in requests[].
/// @param recipient_ address to repay the loan to.
/// @param isCallback_ true if the lender implements the CoolerCallback abstract. False otherwise.
/// @return loanID of the granted loan. Equivalent to the index of loan in loans[].
function clearRequest(
uint256 reqID_,
address recipient_,
bool isCallback_
) external returns (uint256 loanID) {
Request memory req = requests[reqID_];
// Loan callbacks are only allowed if:
// 1. The loan request has been created via a trusted lender.
// 2. The lender signals that it implements the CoolerCallback Abstract.
bool callback = (isCallback_ && msg.sender == req.requester);
// If necessary, ensure lender implements the CoolerCallback abstract.
if (callback && !CoolerCallback(msg.sender).isCoolerCallback()) revert NotCoolerCallback();
// Ensure loan request is active.
if (!req.active) revert Deactivated();
// Clear the loan request in memory.
req.active = false;
// Calculate and store loan terms.
uint256 interest = interestFor(req.amount, req.interest, req.duration);
uint256 collat = collateralFor(req.amount, req.loanToCollateral);
loanID = loans.length;
loans.push(
Loan({
request: req,
principal: req.amount,
interestDue: interest,
collateral: collat,
expiry: block.timestamp + req.duration,
lender: msg.sender,
recipient: recipient_,
callback: callback
})
);
// Clear the loan request storage.
requests[reqID_].active = false;
// Transfer debt tokens to the owner of the request.
debt().safeTransferFrom(msg.sender, owner(), req.amount);
// Log the event.
factory().logClearRequest(reqID_, loanID);
}
/// @notice Allow lender to extend a loan for the borrower. Doesn't require
/// borrower permission because it doesn't have a negative impact for them.
/// @dev Since this function solely impacts the expiration day, the lender
/// should ensure that extension interest payments are done beforehand.
/// @param loanID_ index of loan in loans[].
/// @param times_ that the fixed-term loan duration is extended.
function extendLoanTerms(uint256 loanID_, uint8 times_) external {
Loan memory loan = loans[loanID_];
if (msg.sender != loan.lender) revert OnlyApproved();
if (block.timestamp > loan.expiry) revert Default();
// Update loan terms to reflect the extension.
loan.expiry += loan.request.duration * times_;
// Save updated loan info in storage.
loans[loanID_] = loan;
// Log the event.
factory().logExtendLoan(loanID_, times_);
}
/// @notice Claim collateral upon loan default.
/// @param loanID_ index of loan in loans[].
/// @return defaulted debt by the borrower, collateral kept by the lender, elapsed time since expiry.
function claimDefaulted(uint256 loanID_) external returns (uint256, uint256, uint256, uint256) {
Loan memory loan = loans[loanID_];
if (block.timestamp <= loan.expiry) revert NotExpired();
loans[loanID_].principal = 0;
loans[loanID_].interestDue = 0;
loans[loanID_].collateral = 0;
// Transfer defaulted collateral to the lender.
collateral().safeTransfer(loan.lender, loan.collateral);
// Log the event.
factory().logDefaultLoan(loanID_, loan.collateral);
// If necessary, trigger lender callback.
if (loan.callback) {
CoolerCallback(loan.lender).onDefault(
loanID_,
loan.principal,
loan.interestDue,
loan.collateral
);
}
return (loan.principal, loan.interestDue, loan.collateral, block.timestamp - loan.expiry);
}
/// @notice Approve transfer of loan ownership rights to a new address.
/// @param to_ address to be approved.
/// @param loanID_ index of loan in loans[].
function approveTransfer(address to_, uint256 loanID_) external {
if (msg.sender != loans[loanID_].lender) revert OnlyApproved();
// Update transfer approvals.
approvals[loanID_] = to_;
}
/// @notice Execute loan ownership transfer. Must be previously approved by the lender.
/// @param loanID_ index of loan in loans[].
function transferOwnership(uint256 loanID_) external {
if (msg.sender != approvals[loanID_]) revert OnlyApproved();
// Update the load lender and the recipient.
loans[loanID_].lender = msg.sender;
loans[loanID_].recipient = msg.sender;
// Callbacks are disabled when transferring ownership.
loans[loanID_].callback = false;
// Clear transfer approvals.
approvals[loanID_] = address(0);
}
/// @notice Allow lender to set repayment recipient of a given loan.
/// @param loanID_ of lender's loan.
/// @param recipient_ reciever of repayments
function setRepaymentAddress(uint256 loanID_, address recipient_) external {
if (msg.sender != loans[loanID_].lender) revert OnlyApproved();
// Update the repayment method.
loans[loanID_].recipient = recipient_;
}
// --- AUX FUNCTIONS ---------------------------------------------
/// @notice Compute collateral needed for a desired loan amount at given loan to collateral ratio.
/// @param principal_ amount of debt tokens.
/// @param loanToCollateral_ ratio for loan. Expressed in 10**collateral().decimals().
function collateralFor(
uint256 principal_,
uint256 loanToCollateral_
) public view returns (uint256) {
return (principal_ * (10 ** collateral().decimals())) / loanToCollateral_;
}
/// @notice Compute interest cost on amount for duration at given annualized rate.
/// @param principal_ amount of debt tokens.
/// @param rate_ of interest (annualized).
/// @param duration_ of the loan in seconds.
/// @return Interest in debt token terms.
function interestFor(
uint256 principal_,
uint256 rate_,
uint256 duration_
) public pure returns (uint256) {
uint256 interest = (rate_ * duration_) / 365 days;
return (principal_ * interest) / DECIMALS_INTEREST;
}
/// @notice Check if given loan has expired.
/// @param loanID_ index of loan in loans[].
/// @return Expiration status.
function hasExpired(uint256 loanID_) external view returns (bool) {
return block.timestamp > loans[loanID_].expiry;
}
/// @notice Check if a given request is active.
/// @param reqID_ index of request in requests[].
/// @return Active status.
function isActive(uint256 reqID_) external view returns (bool) {
return requests[reqID_].active;
}
/// @notice Getter for Request data as a struct.
/// @param reqID_ index of request in requests[].
/// @return Request struct.
function getRequest(uint256 reqID_) external view returns (Request memory) {
return requests[reqID_];
}
/// @notice Getter for Loan data as a struct.
/// @param loanID_ index of loan in loans[].
/// @return Loan struct.
function getLoan(uint256 loanID_) external view returns (Loan memory) {
return loans[loanID_];
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {CoolerFactory} from "./CoolerFactory.sol";
/// @notice Allows for debt issuers to execute logic when a loan is repaid, rolled, or defaulted.
/// @dev The three callback functions must be implemented if `isCoolerCallback()` is set to true.
abstract contract CoolerCallback {
// --- ERRORS ----------------------------------------------------
error OnlyFromFactory();
// --- INITIALIZATION --------------------------------------------
CoolerFactory public immutable factory;
constructor(address coolerFactory_) {
factory = CoolerFactory(coolerFactory_);
}
// --- EXTERNAL FUNCTIONS ------------------------------------------------
/// @notice Informs to Cooler that this contract can handle its callbacks.
function isCoolerCallback() external pure returns (bool) {
return true;
}
/// @notice Callback function that handles repayments.
function onRepay(uint256 loanID_, uint256 principlePaid_, uint256 interestPaid_) external {
if (!factory.created(msg.sender)) revert OnlyFromFactory();
_onRepay(loanID_, principlePaid_, interestPaid_);
}
/// @notice Callback function that handles defaults.
function onDefault(
uint256 loanID_,
uint256 principle,
uint256 interest,
uint256 collateral
) external {
if (!factory.created(msg.sender)) revert OnlyFromFactory();
_onDefault(loanID_, principle, interest, collateral);
}
// --- INTERNAL FUNCTIONS ------------------------------------------------
/// @notice Callback function that handles repayments. Override for custom logic.
function _onRepay(
uint256 loanID_,
uint256 principlePaid_,
uint256 interestPaid_
) internal virtual;
/// @notice Callback function that handles defaults.
function _onDefault(
uint256 loanID_,
uint256 principle_,
uint256 interestDue_,
uint256 collateral
) internal virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ClonesWithImmutableArgs} from "clones/ClonesWithImmutableArgs.sol";
import {Cooler} from "./Cooler.sol";
/// @title Cooler Loans Factory.
/// @notice The Cooler Factory creates new Cooler escrow contracts.
/// @dev This contract uses Clones (https://github.com/wighawag/clones-with-immutable-args)
/// to save gas on deployment.
contract CoolerFactory {
using ClonesWithImmutableArgs for address;
// --- ERRORS ----------------------------------------------------
error NotFromFactory();
error DecimalsNot18();
// --- EVENTS ----------------------------------------------------
/// @notice A global event when a new loan request is created.
event RequestLoan(address indexed cooler, address collateral, address debt, uint256 reqID);
/// @notice A global event when a loan request is rescinded.
event RescindRequest(address indexed cooler, uint256 reqID);
/// @notice A global event when a loan request is fulfilled.
event ClearRequest(address indexed cooler, uint256 reqID, uint256 loanID);
/// @notice A global event when a loan is repaid.
event RepayLoan(address indexed cooler, uint256 loanID, uint256 amount);
/// @notice A global event when a loan is extended.
event ExtendLoan(address indexed cooler, uint256 loanID, uint8 times);
/// @notice A global event when the collateral of defaulted loan is claimed.
event DefaultLoan(address indexed cooler, uint256 loanID, uint256 amount);
// -- STATE VARIABLES --------------------------------------------
/// @notice Cooler reference implementation (deployed on creation to clone from).
Cooler public immutable coolerImplementation;
/// @notice Mapping to validate deployed coolers.
mapping(address => bool) public created;
/// @notice Mapping to prevent duplicate coolers.
mapping(address => mapping(ERC20 => mapping(ERC20 => address))) private coolerFor;
/// @notice Mapping to query Coolers for Collateral-Debt pair.
mapping(ERC20 => mapping(ERC20 => address[])) public coolersFor;
// --- INITIALIZATION --------------------------------------------
constructor() {
coolerImplementation = new Cooler();
}
// --- DEPLOY NEW COOLERS ----------------------------------------
/// @notice creates a new Escrow contract for collateral and debt tokens.
/// @param collateral_ the token given as collateral.
/// @param debt_ the token to be lent. Interest is denominated in debt tokens.
/// @return cooler address of the contract.
function generateCooler(ERC20 collateral_, ERC20 debt_) external returns (address cooler) {
// Return address if cooler exists.
cooler = coolerFor[msg.sender][collateral_][debt_];
// Otherwise generate new cooler.
if (cooler == address(0)) {
if (collateral_.decimals() != 18 || debt_.decimals() != 18) revert DecimalsNot18();
// Clone the cooler implementation.
bytes memory coolerData = abi.encodePacked(
msg.sender, // owner
address(collateral_), // collateral
address(debt_), // debt
address(this) // factory
);
cooler = address(coolerImplementation).clone(coolerData);
// Update storage accordingly.
coolerFor[msg.sender][collateral_][debt_] = cooler;
coolersFor[collateral_][debt_].push(cooler);
created[cooler] = true;
}
}
// --- EMIT EVENTS -----------------------------------------------
/// @notice Ensure that the called is a Cooler.
modifier onlyFromFactory() {
if (!created[msg.sender]) revert NotFromFactory();
_;
}
/// @notice Emit a global event when a new loan request is created.
function logRequestLoan(uint256 reqID_) external onlyFromFactory {
emit RequestLoan(
msg.sender,
address(Cooler(msg.sender).collateral()),
address(Cooler(msg.sender).debt()),
reqID_
);
}
/// @notice Emit a global event when a loan request is rescinded.
function logRescindRequest(uint256 reqID_) external onlyFromFactory {
emit RescindRequest(msg.sender, reqID_);
}
/// @notice Emit a global event when a loan request is fulfilled.
function logClearRequest(uint256 reqID_, uint256 loanID_) external onlyFromFactory {
emit ClearRequest(msg.sender, reqID_, loanID_);
}
/// @notice Emit a global event when a loan is repaid.
function logRepayLoan(uint256 loanID_, uint256 repayment_) external onlyFromFactory {
emit RepayLoan(msg.sender, loanID_, repayment_);
}
/// @notice Emit a global event when a loan is extended.
function logExtendLoan(uint256 loanID_, uint8 times_) external onlyFromFactory {
emit ExtendLoan(msg.sender, loanID_, times_);
}
/// @notice Emit a global event when the collateral of defaulted loan is claimed.
function logDefaultLoan(uint256 loanID_, uint256 collateral_) external onlyFromFactory {
emit DefaultLoan(msg.sender, loanID_, collateral_);
}
// --- AUX FUNCTIONS ---------------------------------------------
/// @notice Getter function to get an existing cooler for a given user <> collateral <> debt combination.
function getCoolerFor(
address user_,
address collateral_,
address debt_
) public view returns (address) {
return coolerFor[user_][ERC20(collateral_)][ERC20(debt_)];
}
}
// SPDX-License-Identifier: GLP-3.0
pragma solidity ^0.8.15;
// Interfaces
import {IERC3156FlashBorrower} from "src/interfaces/maker-dao/IERC3156FlashBorrower.sol";
import {IERC3156FlashLender} from "src/interfaces/maker-dao/IERC3156FlashLender.sol";
import {IDaiUsdsMigrator} from "src/interfaces/maker-dao/IDaiUsdsMigrator.sol";
import {ICoolerV2Migrator} from "./interfaces/ICoolerV2Migrator.sol";
import {IEnabler} from "./interfaces/IEnabler.sol";
// Libraries
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {SafeCast} from "src/libraries/SafeCast.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {Owned} from "solmate/auth/Owned.sol";
// Bophades
import {Cooler} from "src/external/cooler/Cooler.sol";
import {CHREGv1} from "src/modules/CHREG/CHREG.v1.sol";
import {IMonoCooler} from "src/policies/interfaces/cooler/IMonoCooler.sol";
import {ICoolerFactory} from "src/external/cooler/interfaces/ICoolerFactory.sol";
import {IDLGTEv1} from "src/modules/DLGTE/IDLGTE.v1.sol";
/// @title CoolerV2Migrator
/// @notice A contract that migrates debt from Olympus Cooler V1 facilities to Cooler V2.
/// This is compatible with all three versions of Cooler V1.
/// @dev This contract uses the `IERC3156FlashBorrower` interface to interact with Maker flashloans.
/// The debt token of MonoCooler is assumed to be USDS. If that is changed in the future, this contract will need to be re-deployed.
contract CoolerV2Migrator is
IERC3156FlashBorrower,
ICoolerV2Migrator,
ReentrancyGuard,
Owned,
IEnabler
{
using SafeCast for uint256;
using SafeTransferLib for ERC20;
// ========= DATA STRUCTURES ========= //
/// @notice Data structure used to store data about a Cooler
struct CoolerData {
Cooler cooler;
ERC20 debtToken;
uint8 numLoans;
}
/// @dev Temporary storage for the principal and interest for each debt token
struct CoolerTotal {
uint256 daiPrincipal;
uint256 daiInterest;
uint256 usdsPrincipal;
uint256 usdsInterest;
}
/// @notice Data structure used for flashloan parameters
struct FlashLoanData {
CoolerData[] coolers;
address currentOwner;
address newOwner;
uint256 usdsRequired;
IDLGTEv1.DelegationRequest[] delegationRequests;
}
// ========= STATE ========= //
/// @notice Whether the contract is enabled
bool public isEnabled;
/// @notice The Clearinghouse registry module
CHREGv1 public immutable CHREG;
/// @notice The DAI token
ERC20 public immutable DAI;
/// @notice The USDS token
ERC20 public immutable USDS;
/// @notice The gOHM token
ERC20 public immutable GOHM;
/// @notice The DAI <> USDS Migrator
IDaiUsdsMigrator public immutable MIGRATOR;
/// @notice The ERC3156 flash loan provider
IERC3156FlashLender public immutable FLASH;
/// @notice The Cooler V2 contract
IMonoCooler public immutable COOLERV2;
/// @notice The list of CoolerFactories
address[] internal _COOLER_FACTORIES;
/// @notice This constant is used when iterating through the loans of a Cooler
/// @dev This is used to prevent infinite loops, and is an appropriate upper bound
/// as the maximum number of loans seen per Cooler is less than 50.
uint8 internal constant MAX_LOANS = type(uint8).max;
// ========= CONSTRUCTOR ========= //
constructor(
address owner_,
address coolerV2_,
address dai_,
address usds_,
address gohm_,
address migrator_,
address flash_,
address chreg_,
address[] memory coolerFactories_
) Owned(owner_) {
// Validate
if (coolerV2_ == address(0)) revert Params_InvalidAddress("coolerV2");
if (dai_ == address(0)) revert Params_InvalidAddress("dai");
if (usds_ == address(0)) revert Params_InvalidAddress("usds");
if (gohm_ == address(0)) revert Params_InvalidAddress("gohm");
if (migrator_ == address(0)) revert Params_InvalidAddress("migrator");
if (flash_ == address(0)) revert Params_InvalidAddress("flash");
if (chreg_ == address(0)) revert Params_InvalidAddress("chreg");
COOLERV2 = IMonoCooler(coolerV2_);
// Validate tokens
if (address(COOLERV2.collateralToken()) != gohm_) revert Params_InvalidAddress("gohm");
if (address(COOLERV2.debtToken()) != usds_) revert Params_InvalidAddress("usds");
DAI = ERC20(dai_);
USDS = ERC20(usds_);
GOHM = ERC20(gohm_);
MIGRATOR = IDaiUsdsMigrator(migrator_);
FLASH = IERC3156FlashLender(flash_);
CHREG = CHREGv1(chreg_);
// Validate the cooler factories for duplicates
for (uint256 i; i < coolerFactories_.length; i++) {
if (coolerFactories_[i] == address(0)) revert Params_InvalidAddress("zero");
// Check for duplicates
for (uint256 j; j < i; j++) {
if (coolerFactories_[j] == coolerFactories_[i])
revert Params_InvalidAddress("duplicate");
}
_COOLER_FACTORIES.push(coolerFactories_[i]);
// Emit the event
emit CoolerFactoryAdded(coolerFactories_[i]);
}
}
// ========= OPERATION ========= //
/// @inheritdoc ICoolerV2Migrator
function previewConsolidate(
address[] memory coolers_
) external view onlyEnabled returns (uint256 collateralAmount, uint256 borrowAmount) {
address[] memory clearinghouses = _getClearinghouses();
address[] memory coolers = new address[](coolers_.length);
// Determine the totals
for (uint256 i; i < coolers_.length; i++) {
// Check that the CoolerFactory created the Cooler
if (!_isValidCooler(coolers_[i])) revert Params_InvalidCooler();
// Check that the Cooler is not already in the array
for (uint256 j; j < coolers.length; j++) {
if (coolers[j] == coolers_[i]) revert Params_DuplicateCooler();
}
// Add the Cooler to the array
coolers[i] = coolers_[i];
Cooler cooler = Cooler(coolers_[i]);
(
uint256 principal,
uint256 interest,
uint256 collateral,
address debtToken,
) = _getDebtForCooler(cooler, clearinghouses);
// Check that the debt token is DAI or USDS
if (debtToken != address(DAI) && debtToken != address(USDS))
revert Params_InvalidCooler();
collateralAmount += collateral;
borrowAmount += principal + interest;
}
// Lender contract is immutable and the fee is also hard-coded to 0. No need to calculate.
return (collateralAmount, borrowAmount);
}
/// @inheritdoc ICoolerV2Migrator
/// @dev This function will revert if:
/// - Any of the Coolers are not owned by the caller.
/// - Any of the Coolers have not been created by the CoolerFactory.
/// - Any of the Coolers have a different lender than an Olympus Clearinghouse.
/// - A duplicate Cooler is provided.
/// - The owner of the destination Cooler V2 has not provided authorization for this contract to manage their Cooler V2 position.
/// - The caller has not approved this contract to spend the collateral token, gOHM.
/// - The contract is not active.
/// - Re-entrancy is detected.
function consolidate(
address[] memory coolers_,
address newOwner_,
IMonoCooler.Authorization memory authorization_,
IMonoCooler.Signature calldata signature_,
IDLGTEv1.DelegationRequest[] calldata delegationRequests_
) external onlyEnabled nonReentrant {
// Validate that the Clearinghouses and Coolers are protocol-owned
// Also calculate the principal and interest for each cooler
CoolerData[] memory coolerData = new CoolerData[](coolers_.length);
// Keep track of the total principal and interest for each debt token
CoolerTotal memory totals;
{
address[] memory clearinghouses = _getClearinghouses();
for (uint256 i; i < coolers_.length; i++) {
// Check that the CoolerFactory created the Cooler
if (!_isValidCooler(coolers_[i])) revert Params_InvalidCooler();
// Check that the Cooler is owned by the caller
Cooler cooler = Cooler(coolers_[i]);
if (cooler.owner() != msg.sender) revert Only_CoolerOwner();
// Check that the Cooler is not already in the array
for (uint256 j; j < coolerData.length; j++) {
if (address(coolerData[j].cooler) == coolers_[i])
revert Params_DuplicateCooler();
}
// Determine the total principal and interest for the cooler
(
uint256 coolerPrincipal,
uint256 coolerInterest,
,
address debtToken,
uint8 numLoans
) = _getDebtForCooler(cooler, clearinghouses);
coolerData[i] = CoolerData({
cooler: cooler,
debtToken: ERC20(debtToken),
numLoans: numLoans
});
if (debtToken == address(DAI)) {
totals.daiPrincipal += coolerPrincipal;
totals.daiInterest += coolerInterest;
} else if (debtToken == address(USDS)) {
totals.usdsPrincipal += coolerPrincipal;
totals.usdsInterest += coolerInterest;
} else {
// Unsupported debt token
revert Params_InvalidCooler();
}
}
}
// Set the Cooler V2 authorization signature, if provided
// If the new owner cannot provide a signature (e.g. multisig), they can call `IMonoCooler.setAuthorization()` instead
if (authorization_.account != address(0)) {
// Validate that authorization provider and new owner matches
if (authorization_.account != newOwner_) revert Params_InvalidNewOwner();
// Authorize this contract to manage user Cooler V2 position
COOLERV2.setAuthorizationWithSig(authorization_, signature_);
}
// Take flashloan
// This will trigger the `onFlashLoan` function after the flashloan amount has been transferred to this contract
{
// Calculate the flashloan amount
uint256 flashloanAmount = totals.daiPrincipal +
totals.usdsPrincipal +
totals.daiInterest +
totals.usdsInterest;
FLASH.flashLoan(
this,
address(DAI),
flashloanAmount,
abi.encode(
FlashLoanData(
coolerData,
msg.sender,
newOwner_,
totals.usdsPrincipal + totals.usdsInterest, // The amount of DAI that will be migrated to USDS
delegationRequests_
)
)
);
}
// This shouldn't happen, but transfer any leftover funds back to the sender
uint256 usdsBalanceAfter = USDS.balanceOf(address(this));
if (usdsBalanceAfter > 0) {
USDS.safeTransfer(msg.sender, usdsBalanceAfter);
emit TokenRefunded(address(USDS), msg.sender, usdsBalanceAfter);
}
uint256 daiBalanceAfter = DAI.balanceOf(address(this));
if (daiBalanceAfter > 0) {
DAI.safeTransfer(msg.sender, daiBalanceAfter);
emit TokenRefunded(address(DAI), msg.sender, daiBalanceAfter);
}
}
/// @inheritdoc IERC3156FlashBorrower
/// @dev This function reverts if:
/// - The caller is not the flash loan provider
/// - The initiator is not this contract
function onFlashLoan(
address initiator_,
address, // flashloan token is only DAI
uint256 amount_,
uint256, // lender fee is 0
bytes calldata params_
) external override returns (bytes32) {
// perform sanity checks
if (msg.sender != address(FLASH)) revert OnlyLender();
if (initiator_ != address(this)) revert OnlyThis();
// Unpack param data
FlashLoanData memory flashLoanData = abi.decode(params_, (FlashLoanData));
CoolerData[] memory coolers = flashLoanData.coolers;
// If there are loans in USDS, convert the required amount from DAI
// The DAI can come from the flash loan or the caller
if (flashLoanData.usdsRequired > 0) {
DAI.safeApprove(address(MIGRATOR), flashLoanData.usdsRequired);
MIGRATOR.daiToUsds(address(this), flashLoanData.usdsRequired);
}
// Keep track of debt tokens out and collateral in
uint256 totalPrincipal;
uint256 totalInterest;
uint256 totalCollateral;
// Validate and repay loans for each cooler
for (uint256 i; i < coolers.length; ++i) {
CoolerData memory coolerData = coolers[i];
(uint256 principal, uint256 interest, uint256 collateral) = _handleRepayments(
coolerData.cooler,
coolerData.debtToken,
coolerData.numLoans
);
totalPrincipal += principal;
totalInterest += interest;
totalCollateral += collateral;
}
// Transfer the collateral from the cooler owner to this contract
GOHM.safeTransferFrom(flashLoanData.currentOwner, address(this), totalCollateral);
// Approve the Cooler V2 to spend the collateral
GOHM.safeApprove(address(COOLERV2), totalCollateral);
// Calculate the amount to borrow from Cooler V2
// The LTC of Cooler V1 is fixed, and the LTC of Cooler V2 is higher at the outset
// Lender fee will be 0
uint256 borrowAmount = totalPrincipal + totalInterest;
// Add collateral and borrow spent flash loan from Cooler V2
COOLERV2.addCollateral(
totalCollateral.encodeUInt128(),
flashLoanData.newOwner,
flashLoanData.delegationRequests
);
COOLERV2.borrow(borrowAmount.encodeUInt128(), flashLoanData.newOwner, address(this));
// Convert the USDS to DAI
uint256 usdsBalance = USDS.balanceOf(address(this));
if (usdsBalance > 0) {
USDS.safeApprove(address(MIGRATOR), usdsBalance);
MIGRATOR.usdsToDai(address(this), usdsBalance);
}
// Approve the flash loan provider to collect the flashloan amount and fee (0)
// The initiator will transfer any remaining DAI and USDS back to the caller
DAI.safeApprove(address(FLASH), amount_);
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
function _handleRepayments(
Cooler cooler_,
ERC20 debtToken_,
uint256 numLoans_
) internal returns (uint256 principal, uint256 interest, uint256 collateral) {
// Provide upfront infinite approval to cooler
// The consolidate() function is gated by a nonReentrant modifier, so there cannot be a reentrancy attack during the approval and revocation
debtToken_.safeApprove(address(cooler_), type(uint256).max);
// Iterate through and repay loans
for (uint256 i; i < numLoans_; i++) {
Cooler.Loan memory loan = cooler_.getLoan(i);
// Only repay outstanding loans
if (loan.principal > 0) {
principal += loan.principal;
interest += loan.interestDue;
collateral += loan.collateral;
cooler_.repayLoan(i, loan.principal + loan.interestDue);
}
}
// Revoke approval
debtToken_.safeApprove(address(cooler_), 0);
return (principal, interest, collateral);
}
// ========= HELPER FUNCTIONS ========= //
function _getDebtForCooler(
Cooler cooler_,
address[] memory clearinghouses_
)
internal
view
returns (
uint256 coolerPrincipal,
uint256 coolerInterest,
uint256 coolerCollateral,
address debtToken,
uint8 numLoans
)
{
// Determine the debt token
debtToken = address(cooler_.debt());
// The Cooler contract does not expose the number of loans, so we iterate until the call reverts
uint8 i;
for (i; i < MAX_LOANS; i++) {
try cooler_.getLoan(i) returns (Cooler.Loan memory loan) {
// Each loan is issued by a Clearinghouse, so we need to check that the Clearinghouse is valid
if (!_inArray(clearinghouses_, loan.lender)) revert Params_InvalidCooler();
// Interest is paid down first, so if the principal is 0, the loan has been paid off
if (loan.principal > 0) {
coolerPrincipal += loan.principal;
coolerInterest += loan.interestDue;
coolerCollateral += loan.collateral;
}
} catch Panic(uint256 /*errorCode*/) {
break;
}
}
return (coolerPrincipal, coolerInterest, coolerCollateral, debtToken, i);
}
/// @notice Check if a given cooler was created by the CoolerFactory
///
/// @param cooler_ Cooler contract
/// @return bool Whether the cooler was created by the CoolerFactory
function _isValidCooler(address cooler_) internal view returns (bool) {
for (uint256 i; i < _COOLER_FACTORIES.length; i++) {
if (ICoolerFactory(_COOLER_FACTORIES[i]).created(cooler_)) {
return true;
}
}
return false;
}
function _inArray(address[] memory array_, address item_) internal pure returns (bool) {
// Check that the item is in the list
for (uint256 i; i < array_.length; i++) {
if (array_[i] == item_) {
return true;
}
}
return false;
}
/// @notice Get all of the clearinghouses in the registry
///
/// @return clearinghouses The list of clearinghouses
function _getClearinghouses() internal view returns (address[] memory clearinghouses) {
uint256 registryCount = CHREG.registryCount();
clearinghouses = new address[](registryCount);
for (uint256 i; i < registryCount; i++) {
clearinghouses[i] = CHREG.registry(i);
}
return clearinghouses;
}
// ============ ADMIN FUNCTIONS ============ //
/// @notice Add a CoolerFactory to the migrator
///
/// @param coolerFactory_ The CoolerFactory to add
function addCoolerFactory(address coolerFactory_) external onlyOwner {
// Validate that the CoolerFactory is not the zero address
if (coolerFactory_ == address(0)) revert Params_InvalidAddress("zero");
// Validate that the CoolerFactory is not already in the array
for (uint256 i; i < _COOLER_FACTORIES.length; i++) {
if (_COOLER_FACTORIES[i] == coolerFactory_) revert Params_InvalidAddress("duplicate");
}
// Add the CoolerFactory to the array
_COOLER_FACTORIES.push(coolerFactory_);
// Emit the event
emit CoolerFactoryAdded(coolerFactory_);
}
/// @notice Remove a CoolerFactory from the migrator
///
/// @param coolerFactory_ The CoolerFactory to remove
function removeCoolerFactory(address coolerFactory_) external onlyOwner {
// Remove the CoolerFactory from the array
bool found;
for (uint256 i; i < _COOLER_FACTORIES.length; i++) {
if (_COOLER_FACTORIES[i] == coolerFactory_) {
_COOLER_FACTORIES[i] = _COOLER_FACTORIES[_COOLER_FACTORIES.length - 1];
_COOLER_FACTORIES.pop();
found = true;
break;
}
}
if (!found) revert Params_InvalidAddress("not found");
// Emit the event
emit CoolerFactoryRemoved(coolerFactory_);
}
/// @notice Get the list of CoolerFactories
///
/// @return coolerFactories The list of CoolerFactories
function getCoolerFactories() external view returns (address[] memory coolerFactories) {
return _COOLER_FACTORIES;
}
// ============ ENABLER FUNCTIONS ============ //
modifier onlyEnabled() {
if (!isEnabled) revert NotEnabled();
_;
}
/// @inheritdoc IEnabler
function enable(bytes calldata) external onlyOwner {
// Validate that the contract is disabled
if (isEnabled) revert NotDisabled();
// Enable the contract
isEnabled = true;
// Emit the enabled event
emit Enabled();
}
/// @inheritdoc IEnabler
function disable(bytes calldata) external onlyEnabled onlyOwner {
// Disable the contract
isEnabled = false;
// Emit the disabled event
emit Disabled();
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import {IERC20} from "src/interfaces/IERC20.sol";
interface ICoolerFactory {
// ===== ERRORS ===== //
error NotFromFactory();
error DecimalsNot18();
// ===== EVENTS ===== //
/// @notice A global event when a new loan request is created.
event RequestLoan(address indexed cooler, address collateral, address debt, uint256 reqID);
/// @notice A global event when a loan request is rescinded.
event RescindRequest(address indexed cooler, uint256 reqID);
/// @notice A global event when a loan request is fulfilled.
event ClearRequest(address indexed cooler, uint256 reqID, uint256 loanID);
/// @notice A global event when a loan is repaid.
event RepayLoan(address indexed cooler, uint256 loanID, uint256 amount);
/// @notice A global event when a loan is extended.
event ExtendLoan(address indexed cooler, uint256 loanID, uint8 times);
/// @notice A global event when the collateral of defaulted loan is claimed.
event DefaultLoan(address indexed cooler, uint256 loanID, uint256 amount);
// ===== COOLER FUNCTIONS ===== //
/// @notice Generate a new cooler.
///
/// @param collateral_ The collateral token.
/// @param debt_ The debt token.
/// @return cooler The address of the new cooler.
function generateCooler(IERC20 collateral_, IERC20 debt_) external returns (address cooler);
// ===== AUX FUNCTIONS ===== //
/// @notice Get the cooler for a given user <> collateral <> debt combination.
///
/// @param user_ The user address.
/// @param collateral_ The collateral token.
/// @param debt_ The debt token.
/// @return cooler The address of the cooler.
function getCoolerFor(
address user_,
address collateral_,
address debt_
) external view returns (address cooler);
/// @notice Check if a cooler was created by the factory.
///
/// @param cooler_ The cooler address.
/// @return bool Whether the cooler was created by the factory.
function created(address cooler_) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {IERC20} from "../../../interfaces/IERC20.sol";
/**
* @title Cooler LTV Oracle
* @notice It is a custom oracle (not dependant on external markets/AMMs/dependencies) to give the
* serve both the Origination LTV and Liquidation LTV
* - They are both quoted in [debtToken / collateralToken] units
* - It is a fixed 18dp price
* - Origination LTV updates on a per second basis according to a policy set rate of change (and is up only or flat)
* - Liquidation LTV is a policy set percentage above the Origination LTV
*/
interface ICoolerLtvOracle {
event OriginationLtvSetAt(
uint96 oldOriginationLtv,
uint96 newOriginationLtvTarget,
uint256 targetTime
);
event MaxOriginationLtvDeltaSet(uint256 maxDelta);
event MinOriginationLtvTargetTimeDeltaSet(uint32 maxTargetTimeDelta);
event MaxOriginationLtvRateOfChangeSet(uint96 maxRateOfChange);
event MaxLiquidationLtvPremiumBpsSet(uint96 maxPremiumBps);
event LiquidationLtvPremiumBpsSet(uint96 premiumBps);
error BreachedMaxOriginationLtvDelta(
uint96 oldOriginationLtv,
uint96 newOriginationLtv,
uint256 maxDelta
);
error BreachedMinDateDelta(uint40 targetTime, uint40 currentDate, uint32 maxTargetTimeDelta);
error BreachedMaxOriginationLtvRateOfChange(uint96 targetRateOfChange, uint96 maxRateOfChange);
error CannotDecreaseLtv();
error InvalidParam();
/// @notice The collateral asset of the LTV [debtToken / collateralToken]
function collateralToken() external view returns (IERC20);
/// @notice The debt asset of the LTV [debtToken / collateralToken]
function debtToken() external view returns (IERC20);
/// @notice The current Origination LTV and Liquidation LTV
function currentLtvs() external view returns (uint96 originationLtv, uint96 liquidationLtv);
/// @notice The current Origination LTV
function currentOriginationLtv() external view returns (uint96);
/// @notice The current Liquidation LTV
function currentLiquidationLtv() external view returns (uint96);
/// @notice The maximum allowed Origination LTV change on any single `setOriginationLtvAt()`, in absolute terms
/// between the Origination LTV as of now and the targetOriginationLtv
/// @dev 18 decimal places, 0.20e18 == $0.20.
/// Used as a bound to avoid unintended/fat fingering when updating Origination LTV
function maxOriginationLtvDelta() external view returns (uint96);
/// @notice The minimum time delta required for Origination LTV to reach it's target value when
/// `setOriginationLtvAt()` is called.
/// @dev In seconds.
/// Used as a bound to avoid unintended/fat fingering when updating Origination LTV
function minOriginationLtvTargetTimeDelta() external view returns (uint32);
/// @notice The maximum (positive) rate of change of Origination LTV allowed, when
/// `setOriginationLtvAt()` is called.
/// @dev Units: [Origination LTV / second]
function maxOriginationLtvRateOfChange() external view returns (uint96);
/// @notice The current Origination LTV state data
function originationLtvData()
external
view
returns (
uint96 startingValue,
uint40 startTime,
uint96 targetValue,
uint40 targetTime,
uint96 slope
);
/// @notice The maximum Liquidation LTV premium (in basis points) which is allowed to be set when calling
/// `setLiquidationLtvPremiumBps()`
function maxLiquidationLtvPremiumBps() external view returns (uint16);
/// @notice The premium (in basis points) of the Liquidation LTV above the Origination LTV
function liquidationLtvPremiumBps() external view returns (uint16);
/// @notice Set maximum Liquidation LTV premium (in basis points) which is allowed to be set when calling
/// `setLiquidationLtvPremiumBps()`.
function setLiquidationLtvPremiumBps(uint16 premiumBps) external;
/// @notice Set Liquidation LTV premium (in basis points) of the Liquidation LTV above the Origination LTV
function setMaxLiquidationLtvPremiumBps(uint16 premiumBps) external;
/// @notice Set the maximum allowed Origination LTV change on any single `setOriginationLtvAt()`, in absolute terms
/// between the Origination LTV as of now and the targetOriginationLtv
/// @dev 18 decimal places, 0.20e18 == $0.20
function setMaxOriginationLtvDelta(uint96 maxDelta) external;
/// @notice Set the minimum time delta required for Origination LTV to reach it's target value when
/// `setOriginationLtvAt()` is called.
/// @dev In seconds.
function setMinOriginationLtvTargetTimeDelta(uint32 minTargetTimeDelta) external;
/// @notice Set the maximum (positive) rate of change of Origination LTV allowed, when
/// `setOriginationLtvAt()` is called.
/// @dev Units: [Origination LTV / second]
function setMaxOriginationLtvRateOfChange(
uint96 originationLtvDelta,
uint32 timeDelta
) external;
/// @notice Set the target Origination LTV which will incrementally increase from it's current value to `targetOriginationLtv`
/// between now and `targetTime`.
/// @dev targetTime is unixtime, targetOriginationLtv is 18 decimal places, 1.05e18 == $1.05
function setOriginationLtvAt(uint96 targetOriginationLtv, uint40 targetTime) external;
/// @notice The decimal precision of both the Origination LTV and Liquidation LTV
function DECIMALS() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {IERC20} from "../../../interfaces/IERC20.sol";
/**
* @title Cooler Treasury Borrower
* @notice Policy which can borrow from Treasury on behalf of Cooler
* - Cooler will always represent the debt amount in 18 decimal places.
* - This logic is split out into a separate policy (rather than using `TreasuryCustodian`):
* 1/ So the Cooler debt token can be updated if required in future to another stablecoin without a redeploy of Cooler.
* 2/ In this case, debt is denominated in USDS but stored 'at rest' in Treasury into sUSDS for extra yield.
* - Upon an upgreade, if the actual debt token is changed (with a new deployment of this contract) to a non 18dp asset
* eg USDC, then borrow() and repay() will need to do the conversion.
*/
interface ICoolerTreasuryBorrower {
error OnlyCooler();
error InvalidParam();
error InvalidAddress();
error ExpectedNonZero();
/// @notice Cooler borrows `amount` of `debtToken` from treasury, sent to `recipient`
/// @param amountInWad The amount to borrow. Always 18 decimal places
/// regardless of the `debtToken.decimals()`
/// @dev If the debtToken is 6dp (eg USDC) then this contract needs to handle the conversion internally
function borrow(uint256 amountInWad, address recipient) external;
/// @notice Repay any `debtToken` in this contract back to treasury.
/// @dev Cooler is expected to transfer the amount to this contract prior to calling
function repay() external;
/// @notice Cooler may write off debt in the case of liquidations.
/// @dev This reduces the policies debt to TRSRY
function writeOffDebt(uint256 debtTokenAmount) external;
/// @notice In the case of a Cooler debt token change (eg USDS => USDC), the
/// debt may be manually net settled from the old debt token (in the old cooler treasury borrower)
/// to the new debt token (in the new cooler treasury borrower)
/// @param debtTokenAmount The amount of debt to set in Treasury, in the debtToken.decimals() precision
function setDebt(uint256 debtTokenAmount) external;
/// @notice The token (USD based stablecoin) which Cooler users borrow and repay
function debtToken() external view returns (IERC20);
/// @notice Convert a debt amount in wad (18dp) into the decimals of the `debtToken`
function convertToDebtTokenAmount(
uint256 amountInWad
) external view returns (IERC20 dToken, uint256 dTokenAmount);
/// @notice The decimal precision of the `amountInWad` used in borrow and repay functions.
/// @dev A constant of 18
function DECIMALS() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {IMonoCooler} from "src/policies/interfaces/cooler/IMonoCooler.sol";
import {IDLGTEv1} from "src/modules/DLGTE/IDLGTE.v1.sol";
/// @title Cooler V2 Migrator
/// @notice Interface for contracts that migrate Cooler V1 loans to Cooler V2
interface ICoolerV2Migrator {
// ========= ERRORS ========= //
/// @notice Thrown when the caller is not the contract itself.
error OnlyThis();
/// @notice Thrown when the caller is not the flash lender.
error OnlyLender();
/// @notice Thrown when the Cooler is not owned by the caller
error Only_CoolerOwner();
/// @notice Thrown when the Clearinghouse is not valid
error Params_InvalidClearinghouse();
/// @notice Thrown when the Cooler is not valid
error Params_InvalidCooler();
/// @notice Thrown when the new owner address provided does not match the authorization
error Params_InvalidNewOwner();
/// @notice Thrown when the Cooler is duplicated
error Params_DuplicateCooler();
/// @notice Thrown when the address is invalid
error Params_InvalidAddress(string reason_);
// ========= EVENTS ========= //
/// @notice Emitted when a CoolerFactory is added to the migrator
event CoolerFactoryAdded(address indexed coolerFactory);
/// @notice Emitted when a CoolerFactory is removed from the migrator
event CoolerFactoryRemoved(address indexed coolerFactory);
/// @notice Emitted when a token is refunded to the recipient
event TokenRefunded(address indexed token, address indexed recipient, uint256 amount);
// ========= FUNCTIONS ========= //
/// @notice Preview the consolidation of a set of loans.
///
/// @param coolers_ The Coolers to consolidate the loans from.
/// @return collateralAmount The amount of collateral that will be migrated into Cooler V2.
/// @return borrowAmount The amount of debt that will be borrowed from Cooler V2.
function previewConsolidate(
address[] calldata coolers_
) external view returns (uint256 collateralAmount, uint256 borrowAmount);
/// @notice Consolidate Cooler V1 loans into Cooler V2
///
/// This function supports consolidation of loans from multiple Clearinghouses and Coolers, provided that the caller is the owner.
///
/// The funds for paying interest owed and fees will be borrowed from Cooler V2.
///
/// It is expected that the caller will have already provided approval for this contract to spend the required tokens. See `previewConsolidate()` for more details.
///
/// @dev The implementing function is expected to handle the following:
/// - Ensure that `coolers_` are valid
/// - Ensure that the caller is the owner of the Coolers
/// - Repay all loans in the Coolers
/// - Deposit the collateral into Cooler V2
/// - Borrow the required amount from Cooler V2 to repay the Cooler V1 loans
///
/// @param coolers_ The Coolers from which the loans will be migrated.
/// @param newOwner_ Address of the owner of the Cooler V2 position. This can be the same as the caller, or a different address.
/// @param authorization_ Authorization parameters for the new owner. Set the `account` field to the zero address to indicate that authorization has already been provided through `IMonoCooler.setAuthorization()`.
/// @param signature_ Authorization signature for the new owner. Ignored if `authorization_.account` is the zero address.
/// @param delegationRequests_ Delegation requests for the new owner.
function consolidate(
address[] memory coolers_,
address newOwner_,
IMonoCooler.Authorization memory authorization_,
IMonoCooler.Signature calldata signature_,
IDLGTEv1.DelegationRequest[] calldata delegationRequests_
) external;
// ===== ADMIN FUNCTIONS ===== //
/// @notice Add a CoolerFactory to the migrator
///
/// @param coolerFactory_ The CoolerFactory to add
function addCoolerFactory(address coolerFactory_) external;
/// @notice Remove a CoolerFactory from the migrator
///
/// @param coolerFactory_ The CoolerFactory to remove
function removeCoolerFactory(address coolerFactory_) external;
/// @notice Get the list of CoolerFactories
///
/// @return coolerFactories The list of CoolerFactories
function getCoolerFactories() external view returns (address[] memory coolerFactories);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {IERC20} from "../../interfaces/IERC20.sol";
interface IDLGTEv1 {
// ========= ERRORS ========= //
error DLGTE_InvalidAddress();
error DLGTE_InvalidDelegationRequests();
error DLGTE_TooManyDelegates();
error DLGTE_InvalidDelegateEscrow();
error DLGTE_InvalidAmount();
error DLGTE_ExceededUndelegatedBalance(uint256 balance, uint256 requested);
error DLGTE_ExceededDelegatedBalance(address delegate, uint256 balance, uint256 requested);
error DLGTE_ExceededPolicyAccountBalance(uint256 balance, uint256 requested);
// ========= EVENTS ========= //
event DelegationApplied(address indexed account, address indexed delegate, int256 amount);
event MaxDelegateAddressesSet(address indexed account, uint256 maxDelegateAddresses);
// ========= STRUCTS ======= //
struct DelegationRequest {
/// @dev The address of the delegate
address delegate;
/// @dev The amount to (un)delegate.
/// positive means delegate, negative undelegate.
int256 amount;
}
struct AccountDelegation {
/// @dev The delegate address - the receiver account of the gOHM voting power.
address delegate;
/// @dev The amount of gOHM delegated to `delegate`
uint256 amount;
/// @dev The DelegateEscrow contract address for this `delegate`
address escrow;
}
// ========= FUNCTIONS ========= //
/**
* @notice Set an account to have more or less than the DEFAULT_MAX_DELEGATE_ADDRESSES
* number of delegates.
*/
function setMaxDelegateAddresses(address account, uint32 maxDelegateAddresses) external;
/**
* @notice gOHM is pulled from the calling policy and added to the undelegated balance.
* @dev
* - This gOHM cannot be used for governance voting until it is delegated.
* - Deposted gOHM balances are tracked per policy. policyA cannot withdraw gOHM that policyB deposited
*/
function depositUndelegatedGohm(address onBehalfOf, uint256 amount) external;
/**
* @notice Undelegated gOHM is transferred to the calling policy.
* @dev
* - If `autoRescindMaxNumDelegates` is greater than zero, the delegations will be automatically rescinded if required
* from up to `autoRescindMaxNumDelegates` number of delegate escrows. See `rescindDelegations()` for details
* - Will revert if there is still not enough undelegated gOHM for `onBehalfOf` OR
* if policy is attempting to withdraw more gOHM than it deposited
* Deposted gOHM balances are tracked per policy. policyA cannot withdraw gOHM that policyB deposited
*/
function withdrawUndelegatedGohm(
address onBehalfOf,
uint256 amount,
uint256 autoRescindMaxNumDelegates
) external;
/**
* @notice Apply a set of delegation requests on behalf of a given account.
* - Each delegation request either delegates or undelegates to an address
* - It applies across total gOHM balances for a given account across all calling policies
* So policyA may (un)delegate the account's gOHM set by policyA, B and C
*/
function applyDelegations(
address onBehalfOf,
DelegationRequest[] calldata delegationRequests
)
external
returns (uint256 totalDelegated, uint256 totalUndelegated, uint256 undelegatedBalance);
/**
* @notice Rescind delegations until the amount undelegated for the `onBehalfOf` account
* is greater or equal to `requestedUndelegatedBalance`. No more than `maxNumDelegates`
* will be rescinded as part of this
* @dev
* - Delegations are rescinded by iterating through the delegate addresses for the
* `onBehalfOf` address.
* - No guarantees on the order of who is rescinded -- it may change as delegations are
* removed
* - A calling policy may be able to rescind more than it added via `depositUndelegatedGohm()`
* however the policy cannot then withdraw an amount higher than what it deposited.
* - If the full `requestedUndelegatedBalance` cannot be fulfilled the `actualUndelegatedBalance`
* return parameter may be less than `requestedUndelegatedBalance`. The caller must decide
* on how to handle that.
*/
function rescindDelegations(
address onBehalfOf,
uint256 requestedUndelegatedBalance,
uint256 maxNumDelegates
) external returns (uint256 totalRescinded, uint256 newUndelegatedBalance);
/**
* @notice Report the total delegated and undelegated gOHM balance for an account
* in a given policy
*/
function policyAccountBalances(
address policy,
address account
) external view returns (uint256 gOhmBalance);
/**
* @notice Paginated view of an account's delegations
* @dev This can be called sequentially, increasing the `startIndex` each time by the number of items
* returned in the previous call, until number of items returned is less than `maxItems`
* The `totalAmount` delegated within the return struct is across all policies for that account delegate
*/
function accountDelegationsList(
address account,
uint256 startIndex,
uint256 maxItems
) external view returns (AccountDelegation[] memory delegations);
/**
* @notice A summary of an account's delegations
*/
function accountDelegationSummary(
address account
)
external
view
returns (
uint256 totalGOhm,
uint256 delegatedGOhm,
uint256 numDelegateAddresses,
uint256 maxAllowedDelegateAddresses
);
/**
* @notice The total amount delegated to a particular delegate across all policies,
* and externally made delegations (including any permanent donations)
*/
function totalDelegatedTo(address delegate) external view returns (uint256);
/**
* @notice The maximum number of delegates an account can have accross all policies
*/
function maxDelegateAddresses(address account) external view returns (uint32 result);
/// @notice The gOhm token supplied by accounts
function gOHM() external view returns (IERC20);
/// @notice The default maximum number of addresses an account can delegate to
function DEFAULT_MAX_DELEGATE_ADDRESSES() external view returns (uint32);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
interface IDaiUsdsMigrator {
event DaiToUsds(address indexed caller, address indexed usr, uint256 wad);
event UsdsToDai(address indexed caller, address indexed usr, uint256 wad);
function dai() external view returns (address);
function daiJoin() external view returns (address);
function daiToUsds(address usr, uint256 wad) external;
function usds() external view returns (address);
function usdsJoin() external view returns (address);
function usdsToDai(address usr, uint256 wad) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
// Imported from forge-std
/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
/// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
/// is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256);
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
/// @notice Moves `amount` tokens from the caller's account to `to`.
function transfer(address to, uint256 amount) external returns (bool);
/// @notice Returns the remaining number of tokens that `spender` is allowed
/// to spend on behalf of `owner`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @notice Returns the name of the token.
function name() external view returns (string memory);
/// @notice Returns the symbol of the token.
function symbol() external view returns (string memory);
/// @notice Returns the decimals places of the token.
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2021 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.7.0;
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2021 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.7.0;
import "./IERC3156FlashBorrower.sol";
interface IERC3156FlashLender {
/**
* @dev The amount of currency available to be lent.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(address token) external view returns (uint256);
/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(address token, uint256 amount) external view returns (uint256);
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
/// @title IEnabler
/// @notice Interface for contracts that can be enabled and disabled
/// @dev This is designed for usage by periphery contracts that cannot inherit from `PolicyEnabler`. Authorization is deliberately left open to the implementing contract.
interface IEnabler {
// ============ EVENTS ============ //
/// @notice Emitted when the contract is enabled
event Enabled();
/// @notice Emitted when the contract is disabled
event Disabled();
// ============ ERRORS ============ //
/// @notice Thrown when the contract is not enabled
error NotEnabled();
/// @notice Thrown when the contract is not disabled
error NotDisabled();
// ============ FUNCTIONS ============ //
/// @notice Returns true if the contract is enabled
/// @return enabled True if the contract is enabled, false otherwise
function isEnabled() external view returns (bool enabled);
/// @notice Enables the contract
/// @dev Implementing contracts should implement permissioning logic
///
/// @param enableData_ Optional data to pass to a custom enable function
function enable(bytes calldata enableData_) external;
/// @notice Disables the contract
/// @dev Implementing contracts should implement permissioning logic
///
/// @param disableData_ Optional data to pass to a custom disable function
function disable(bytes calldata disableData_) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {IERC20} from "../../../interfaces/IERC20.sol";
import {IDLGTEv1} from "../../../modules/DLGTE/IDLGTE.v1.sol";
import {ICoolerLtvOracle} from "./ICoolerLtvOracle.sol";
import {ICoolerTreasuryBorrower} from "./ICoolerTreasuryBorrower.sol";
import {IStaking} from "../../../interfaces/IStaking.sol";
/**
* @title Mono Cooler
* @notice A borrow/lend market where users can deposit their gOHM as collateral and then
* borrow a stablecoin debt token up to a certain LTV
* - The debt token may change over time - eg DAI to USDS (or USDC), determined by the
* `CoolerTreasuryBorrower`
* - The collateral and debt amounts tracked on this contract are always reported in wad,
* ie 18 decimal places
* - gOHM collateral can be delegated to accounts for voting, via the DLGTE module
* - Positions can be liquidated if the LTV breaches the 'liquidation LTV' as determined by the
* `LTV Oracle`
* - Users may set an authorization for one other address to act on its behalf.
*/
interface IMonoCooler {
error ExceededMaxOriginationLtv(uint256 newLtv, uint256 maxOriginationLtv);
error ExceededCollateralBalance();
error MinDebtNotMet(uint256 minRequired, uint256 current);
error InvalidAddress();
error InvalidParam();
error ExpectedNonZero();
error Paused();
error CannotLiquidate();
error InvalidDelegationRequests();
error ExceededPreviousLtv(uint256 oldLtv, uint256 newLtv);
error InvalidCollateralDelta();
error ExpiredSignature(uint256 deadline);
error InvalidNonce(uint256 deadline);
error InvalidSigner(address signer, address owner);
error UnauthorizedOnBehalfOf();
event BorrowPausedSet(bool isPaused);
event LiquidationsPausedSet(bool isPaused);
event InterestRateSet(uint96 interestRateWad);
event LtvOracleSet(address indexed oracle);
event TreasuryBorrowerSet(address indexed treasuryBorrower);
event CollateralAdded(
address indexed caller,
address indexed onBehalfOf,
uint128 collateralAmount
);
event CollateralWithdrawn(
address indexed caller,
address indexed onBehalfOf,
address indexed recipient,
uint128 collateralAmount
);
event Borrow(
address indexed caller,
address indexed onBehalfOf,
address indexed recipient,
uint128 amount
);
event Repay(address indexed caller, address indexed onBehalfOf, uint128 repayAmount);
event Liquidated(
address indexed caller,
address indexed account,
uint128 collateralSeized,
uint128 debtWiped,
uint128 incentives
);
event AuthorizationSet(
address indexed caller,
address indexed account,
address indexed authorized,
uint96 authorizationDeadline
);
/// @notice The record of an individual account's collateral and debt data
struct AccountState {
/// @notice The amount of gOHM collateral the account has posted
uint128 collateral;
/**
* @notice A checkpoint of user debt, updated after a borrow/repay/liquidation
* @dev Debt as of now = (
* `account.debtCheckpoint` *
* `debtTokenData.interestAccumulator` /
* `account.interestAccumulator`
* )
*/
uint128 debtCheckpoint;
/// @notice The account's last interest accumulator checkpoint
uint256 interestAccumulatorRay;
}
struct Authorization {
/// @notice The address of the account granting authorization
address account;
/// @notice The address of who is authorized to act on the the accounts behalf
address authorized;
/// @notice The unix timestamp that the access is automatically revoked
uint96 authorizationDeadline;
/// @notice For replay protection
uint256 nonce;
/// @notice A unix timestamp for when the signature is valid until
uint256 signatureDeadline;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
/// @notice The status for whether an account can be liquidated or not
struct LiquidationStatus {
/// @notice The amount [in gOHM collateral terms] of collateral which has been provided by the user
uint128 collateral;
/// @notice The up to date amount of debt [in debtToken terms]
uint128 currentDebt;
/// @notice The current LTV of this account [in debtTokens per gOHM collateral terms]
uint128 currentLtv;
/// @notice Has this account exceeded the liquidation LTV
bool exceededLiquidationLtv;
/// @notice Has this account exceeded the max origination LTV
bool exceededMaxOriginationLtv;
/// @notice A liquidator will receive this amount [in gOHM collateral terms] if
/// this account is liquidated as of this block
uint128 currentIncentive;
}
/// @notice An account's collateral and debt position details
/// Provided for UX
struct AccountPosition {
/// @notice The amount [in gOHM collateral terms] of collateral which has been provided by the user
/// @dev To 18 decimal places
uint256 collateral;
/// @notice The up to date amount of debt
/// @dev To 18 decimal places
uint256 currentDebt;
/// @notice The maximum amount of debtToken's this account can borrow given the
/// collateral posted, up to `maxOriginationLtv`
/// @dev To 18 decimal places
uint256 maxOriginationDebtAmount;
/// @notice The maximum amount of debtToken's this account can accrue before being
/// eligable to be liquidated, up to `liquidationLtv`
/// @dev To 18 decimal places
uint256 liquidationDebtAmount;
/// @notice The health factor of this accounts position.
/// Anything less than 1 can be liquidated, relative to `liquidationLtv`
/// @dev To 18 decimal places
uint256 healthFactor;
/// @notice The current LTV of this account [in debtTokens per gOHM collateral terms]
/// @dev To 18 decimal places
uint256 currentLtv;
/// @notice The total collateral delegated for this user across all delegates
/// @dev To 18 decimal places
uint256 totalDelegated;
/// @notice The current number of addresses this account has delegated to
uint256 numDelegateAddresses;
/// @notice The max number of delegates this account is allowed to delegate to
uint256 maxDelegateAddresses;
}
/// @notice The collateral token supplied by users/accounts, eg gOHM
function collateralToken() external view returns (IERC20);
/// @notice The debt token which can be borrowed, eg DAI or USDS
function debtToken() external view returns (IERC20);
/// @notice Unwrapped gOHM
function ohm() external view returns (IERC20);
/// @notice staking contract to unstake (and burn) OHM from liquidations
function staking() external view returns (IStaking);
/// @notice The minimum debt a user needs to maintain
/// @dev It costs gas to liquidate users, so we don't want dust amounts.
/// To 18 decimal places
function minDebtRequired() external view returns (uint256);
/// @notice The total amount of collateral posted across all accounts.
/// @dev To 18 decimal places
function totalCollateral() external view returns (uint128);
/// @notice The total amount of debt which has been borrowed across all users
/// as of the latest checkpoint
/// @dev To 18 decimal places
function totalDebt() external view returns (uint128);
/// @notice Liquidations may be paused in order for users to recover/repay debt after
/// emergency actions or interest rate changes
function liquidationsPaused() external view returns (bool);
/// @notice Borrows may be paused for emergency actions or deprecating the facility
function borrowsPaused() external view returns (bool);
/// @notice The flat interest rate (APR).
/// @dev Interest (approximately) continuously compounds at this rate.
/// @dev To 18 decimal places
function interestRateWad() external view returns (uint96);
/// @notice The oracle serving both the Max Origination LTV and the Liquidation LTV
function ltvOracle() external view returns (ICoolerLtvOracle);
/// @notice The policy which borrows/repays from Treasury on behalf of Cooler
function treasuryBorrower() external view returns (ICoolerTreasuryBorrower);
/// @notice The current Max Origination LTV and Liquidation LTV from the `ltvOracle()`
/// @dev Both to 18 decimal places
function loanToValues() external view returns (uint96 maxOriginationLtv, uint96 liquidationLtv);
/// @notice The last time the global debt accumulator was updated
function interestAccumulatorUpdatedAt() external view returns (uint40);
/// @notice The accumulator index used to track the compounding of debt, starting at 1e27 at genesis
/// @dev To RAY (1e27) precision
function interestAccumulatorRay() external view returns (uint256);
/// @notice Whether `authorized` is authorized to act on `authorizer`'s behalf for all user actions
/// up until the `authorizationDeadline` unix timestamp.
/// @dev Anyone is authorized to modify their own positions, regardless of this variable.
function authorizations(
address authorizer,
address authorized
) external view returns (uint96 authorizationDeadline);
/// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
function authorizationNonces(address authorizer) external view returns (uint256);
/// @dev Returns the domain separator used in the encoding of the signature for `setAuthorizationWithSig()`, as defined by {EIP712}.
function DOMAIN_SEPARATOR() external view returns (bytes32);
/// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions until `authorizationDeadline`
/// @param authorized The authorized address.
/// @param authorizationDeadline The unix timestamp that they the authorization is valid until.
/// @dev Authorization can be revoked by setting the `authorizationDeadline` into the past
function setAuthorization(address authorized, uint96 authorizationDeadline) external;
/// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions
/// until `authorization.authorizationDeadline`.
/// @dev Warning: Reverts if the signature has already been submitted.
/// @dev The signature is malleable, but it has no impact on the security here.
/// @dev The nonce is passed as argument to be able to revert with a different error message.
/// @param authorization The `Authorization` struct.
/// @param signature The signature.
/// @dev Authorization can be revoked by calling `setAuthorization()` and setting the `authorizationDeadline` into the past
function setAuthorizationWithSig(
Authorization calldata authorization,
Signature calldata signature
) external;
/// @dev Returns whether the `sender` is authorized to manage `onBehalf`'s positions.
function isSenderAuthorized(address sender, address onBehalf) external view returns (bool);
//============================================================================================//
// COLLATERAL //
//============================================================================================//
/**
* @notice Deposit gOHM as collateral
* @param collateralAmount The amount to deposit to 18 decimal places
* - MUST be greater than zero
* @param onBehalfOf A caller can add collateral on behalf of themselves or another address.
* - MUST NOT be address(0)
* @param delegationRequests The set of delegations to apply after adding collateral.
* - MAY be empty, meaning no delegations are applied.
* - MUST ONLY be requests to add delegations, and that total MUST BE less than the `collateralAmount` argument
* - If `onBehalfOf` does not equal the caller, the caller must be authorized via
* `setAuthorization()` or `setAuthorizationWithSig()`
*/
function addCollateral(
uint128 collateralAmount,
address onBehalfOf,
IDLGTEv1.DelegationRequest[] calldata delegationRequests
) external;
/**
* @notice Withdraw gOHM collateral.
* - Account LTV MUST be less than or equal to `maxOriginationLtv` after the withdraw is applied
* - At least `collateralAmount` collateral MUST be undelegated for this account.
* Use the `delegationRequests` to rescind enough as part of this request.
* @param collateralAmount The amount of collateral to remove to 18 decimal places
* - MUST be greater than zero
* - If set to type(uint128).max then withdraw the max amount up to maxOriginationLtv
* @param onBehalfOf A caller can withdraw collateral on behalf of themselves or another address if
* authorized via `setAuthorization()` or `setAuthorizationWithSig()`
* @param recipient Send the gOHM collateral to a specified recipient address.
* - MUST NOT be address(0)
* @param delegationRequests The set of delegations to apply before removing collateral.
* - MAY be empty, meaning no delegations are applied.
* - MUST ONLY be requests to undelegate, and that total undelegated MUST BE less than the `collateralAmount` argument
*/
function withdrawCollateral(
uint128 collateralAmount,
address onBehalfOf,
address recipient,
IDLGTEv1.DelegationRequest[] calldata delegationRequests
) external returns (uint128 collateralWithdrawn);
/**
* @notice Apply a set of delegation requests on behalf of a given user.
* @param delegationRequests The set of delegations to apply.
* - MAY be empty, meaning no delegations are applied.
* - Total collateral delegated as part of these requests MUST BE less than the account collateral.
* - MUST NOT apply delegations that results in more collateral being undelegated than
* the account has collateral for.
* - It applies across total gOHM balances for a given account across all calling policies
* So this may (un)delegate the account's gOHM set by another policy
* @param onBehalfOf A caller can apply delegations on behalf of themselves or another address if
* authorized via `setAuthorization()` or `setAuthorizationWithSig()`
*/
function applyDelegations(
IDLGTEv1.DelegationRequest[] calldata delegationRequests,
address onBehalfOf
)
external
returns (uint256 totalDelegated, uint256 totalUndelegated, uint256 undelegatedBalance);
//============================================================================================//
// BORROW/REPAY //
//============================================================================================//
/**
* @notice Borrow `debtToken`
* - Account LTV MUST be less than or equal to `maxOriginationLtv` after the borrow is applied
* - Total debt for this account MUST be greater than or equal to the `minDebtRequired`
* after the borrow is applied
* @param borrowAmountInWad The amount of `debtToken` to borrow, to 18 decimals regardless of the debt token
* - MUST be greater than zero
* - If set to type(uint128).max then borrow the max amount up to maxOriginationLtv
* @param onBehalfOf A caller can borrow on behalf of themselves or another address if
* authorized via `setAuthorization()` or `setAuthorizationWithSig()`
* @param recipient Send the borrowed token to a specified recipient address.
* - MUST NOT be address(0)
* @return amountBorrowedInWad The amount actually borrowed.
*/
function borrow(
uint128 borrowAmountInWad,
address onBehalfOf,
address recipient
) external returns (uint128 amountBorrowedInWad);
/**
* @notice Repay a portion, or all of the debt
* - MUST NOT be called for an account which has no debt
* - If the entire debt isn't paid off, then the total debt for this account
* MUST be greater than or equal to the `minDebtRequired` after the borrow is applied
* @param repayAmountInWad The amount to repay, to 18 decimals regardless of the debt token
* - MUST be greater than zero
* - MAY be greater than the latest debt as of this block. In which case it will be capped
* to that latest debt
* @param onBehalfOf A caller can repay the debt on behalf of themselves or another address
* @return amountRepaidInWad The amount actually repaid.
*/
function repay(
uint128 repayAmountInWad,
address onBehalfOf
) external returns (uint128 amountRepaidInWad);
//============================================================================================//
// LIQUIDATIONS //
//============================================================================================//
/**
* @notice Liquidate one or more accounts which have exceeded the `liquidationLtv`
* The gOHM collateral is seized (unstaked to OHM and burned), and the accounts debt is wiped.
* @dev
* - If one of the provided accounts in the batch hasn't exceeded the max LTV then it is skipped.
* - Delegations are auto-rescinded if required. Ordering of this is not guaranteed.
*/
function batchLiquidate(
address[] calldata accounts
)
external
returns (
uint128 totalCollateralClaimed,
uint128 totalDebtWiped,
uint128 totalLiquidationIncentive
);
/**
* @notice If an account becomes unhealthy and has many delegations such that liquidation can't be
* performed in one transaction, then delegations can be rescinded over multiple transactions
* in order to get this account into a state where it can then be liquidated.
*/
function applyUnhealthyDelegations(
address account,
uint256 autoRescindMaxNumDelegates
) external returns (uint256 totalUndelegated, uint256 undelegatedBalance);
//============================================================================================//
// ADMIN //
//============================================================================================//
/// @notice Set the oracle which serves the max Origination LTV and the Liquidation LTV
function setLtvOracle(address newOracle) external;
/// @notice Set the policy which borrows/repays from Treasury on behalf of Cooler
function setTreasuryBorrower(address newTreasuryBorrower) external;
/// @notice Liquidation may be paused in order for users to recover/repay debt after emergency actions
/// @dev Can only be called by emergency or admin roles
function setLiquidationsPaused(bool isPaused) external;
/// @notice Pause any new borrows of `debtToken`
/// @dev Can only be called by emergency or admin roles
function setBorrowPaused(bool isPaused) external;
/// @notice Update the interest rate (APR), specified in Wad (18 decimals)
/// @dev
/// - Cannot be set higher than 10% APR
/// - Interest (approximately) continuously compounds at this rate.
function setInterestRateWad(uint96 newInterestRateWad) external;
/// @notice Allow an account to have more or less than the DEFAULT_MAX_DELEGATE_ADDRESSES
/// number of delegates.
function setMaxDelegateAddresses(address account, uint32 maxDelegateAddresses) external;
/// @notice Update and checkpoint the total debt up until now
/// @dev May be useful in case there are no new user actions for some time.
function checkpointDebt()
external
returns (uint128 totalDebtInWad, uint256 interestAccumulatorRay);
//============================================================================================//
// AUX FUNCTIONS //
//============================================================================================//
/**
* @notice Calculate the difference in debt required in order to be at or just under
* the maxOriginationLTV if `collateralDelta` was added/removed
* from the current position.
* A positive `debtDeltaInWad` means the account can borrow that amount after adding that `collateralDelta` collateral
* A negative `debtDeltaInWad` means it needs to repay that amount in order to withdraw that `collateralDelta` collateral
* @dev debtDeltaInWad is always to 18 decimal places
*/
function debtDeltaForMaxOriginationLtv(
address account,
int128 collateralDelta
) external view returns (int128 debtDeltaInWad);
/**
* @notice An view of an accounts current and up to date position as of this block
* @param account The account to get a position for
*/
function accountPosition(
address account
) external view returns (AccountPosition memory position);
/**
* @notice Compute the liquidity status for a set of accounts.
* @dev This can be used to verify if accounts can be liquidated or not.
* @param accounts The accounts to get the status for.
*/
function computeLiquidity(
address[] calldata accounts
) external view returns (LiquidationStatus[] memory status);
/**
* @notice Paginated view of an account's delegations
* @dev Can call sequentially increasing the `startIndex` each time by the number of items returned in the previous call,
* until number of items returned is less than `maxItems`
*/
function accountDelegationsList(
address account,
uint256 startIndex,
uint256 maxItems
) external view returns (IDLGTEv1.AccountDelegation[] memory delegations);
/// @notice A view of the last checkpoint of account data (not as of this block)
function accountState(address account) external view returns (AccountState memory);
/// @notice An account's current collateral
/// @dev to 18 decimal places
function accountCollateral(address account) external view returns (uint128 collateralInWad);
/// @notice An account's current debt as of this block
// @notice to 18 decimal places regardless of the debt token
function accountDebt(address account) external view returns (uint128 debtInWad);
/// @notice A view of the derived/internal cache data.
function globalState()
external
view
returns (uint128 totalDebt, uint256 interestAccumulatorRay);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;
interface IStaking {
/* ========== DATA STRUCTURES ========== */
struct Epoch {
uint256 length; // in seconds
uint256 number; // since inception
uint256 end; // timestamp
uint256 distribute; // amount
}
/* ========== MUTATIVE FUNCTIONS ========== */
function rebase() external returns (uint256);
function unstake(address, uint256, bool _trigger, bool) external returns (uint256);
/* ========== ADMIN FUNCTIONS ========== */
function setDistributor(address _distributor) external;
/* ========== VIEW FUNCTIONS ========== */
function secondsToNextEpoch() external view returns (uint256);
function epoch() external view returns (uint256, uint256, uint256, uint256);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;
// ███████ █████ █████ █████ ██████ ██████ ███████████ █████ █████ █████████
// ███░░░░░███ ░░███ ░░███ ░░███ ░░██████ ██████ ░░███░░░░░███░░███ ░░███ ███░░░░░███
// ███ ░░███ ░███ ░░███ ███ ░███░█████░███ ░███ ░███ ░███ ░███ ░███ ░░░
// ░███ ░███ ░███ ░░█████ ░███░░███ ░███ ░██████████ ░███ ░███ ░░█████████
// ░███ ░███ ░███ ░░███ ░███ ░░░ ░███ ░███░░░░░░ ░███ ░███ ░░░░░░░░███
// ░░███ ███ ░███ █ ░███ ░███ ░███ ░███ ░███ ░███ ███ ░███
// ░░░███████░ ███████████ █████ █████ █████ █████ ░░████████ ░░█████████
// ░░░░░░░ ░░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░░░░░
//============================================================================================//
// GLOBAL TYPES //
//============================================================================================//
/// @notice Actions to trigger state changes in the kernel. Passed by the executor
enum Actions {
InstallModule,
UpgradeModule,
ActivatePolicy,
DeactivatePolicy,
ChangeExecutor,
MigrateKernel
}
/// @notice Used by executor to select an action and a target contract for a kernel action
struct Instruction {
Actions action;
address target;
}
/// @notice Used to define which module functions a policy needs access to
struct Permissions {
Keycode keycode;
bytes4 funcSelector;
}
type Keycode is bytes5;
//============================================================================================//
// UTIL FUNCTIONS //
//============================================================================================//
error TargetNotAContract(address target_);
error InvalidKeycode(Keycode keycode_);
// solhint-disable-next-line func-visibility
function toKeycode(bytes5 keycode_) pure returns (Keycode) {
return Keycode.wrap(keycode_);
}
// solhint-disable-next-line func-visibility
function fromKeycode(Keycode keycode_) pure returns (bytes5) {
return Keycode.unwrap(keycode_);
}
// solhint-disable-next-line func-visibility
function ensureContract(address target_) view {
if (target_.code.length == 0) revert TargetNotAContract(target_);
}
// solhint-disable-next-line func-visibility
function ensureValidKeycode(Keycode keycode_) pure {
bytes5 unwrapped = Keycode.unwrap(keycode_);
for (uint256 i = 0; i < 5; ) {
bytes1 char = unwrapped[i];
if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_); // A-Z only
unchecked {
i++;
}
}
}
//============================================================================================//
// COMPONENTS //
//============================================================================================//
/// @notice Generic adapter interface for kernel access in modules and policies.
abstract contract KernelAdapter {
error KernelAdapter_OnlyKernel(address caller_);
Kernel public kernel;
constructor(Kernel kernel_) {
kernel = kernel_;
}
/// @notice Modifier to restrict functions to be called only by kernel.
modifier onlyKernel() {
if (msg.sender != address(kernel)) revert KernelAdapter_OnlyKernel(msg.sender);
_;
}
/// @notice Function used by kernel when migrating to a new kernel.
function changeKernel(Kernel newKernel_) external onlyKernel {
kernel = newKernel_;
}
}
/// @notice Base level extension of the kernel. Modules act as independent state components to be
/// interacted with and mutated through policies.
/// @dev Modules are installed and uninstalled via the executor.
abstract contract Module is KernelAdapter {
error Module_PolicyNotPermitted(address policy_);
constructor(Kernel kernel_) KernelAdapter(kernel_) {}
/// @notice Modifier to restrict which policies have access to module functions.
modifier permissioned() {
if (
msg.sender == address(kernel) ||
!kernel.modulePermissions(KEYCODE(), Policy(msg.sender), msg.sig)
) revert Module_PolicyNotPermitted(msg.sender);
_;
}
/// @notice 5 byte identifier for a module.
function KEYCODE() public pure virtual returns (Keycode) {}
/// @notice Returns which semantic version of a module is being implemented.
/// @return major - Major version upgrade indicates breaking change to the interface.
/// @return minor - Minor version change retains backward-compatible interface.
function VERSION() external pure virtual returns (uint8 major, uint8 minor) {}
/// @notice Initialization function for the module
/// @dev This function is called when the module is installed or upgraded by the kernel.
/// @dev MUST BE GATED BY onlyKernel. Used to encompass any initialization or upgrade logic.
function INIT() external virtual onlyKernel {}
}
/// @notice Policies are application logic and external interface for the kernel and installed modules.
/// @dev Policies are activated and deactivated in the kernel by the executor.
/// @dev Module dependencies and function permissions must be defined in appropriate functions.
abstract contract Policy is KernelAdapter {
error Policy_ModuleDoesNotExist(Keycode keycode_);
error Policy_WrongModuleVersion(bytes expected_);
constructor(Kernel kernel_) KernelAdapter(kernel_) {}
/// @notice Easily accessible indicator for if a policy is activated or not.
function isActive() external view returns (bool) {
return kernel.isPolicyActive(this);
}
/// @notice Function to grab module address from a given keycode.
function getModuleAddress(Keycode keycode_) internal view returns (address) {
address moduleForKeycode = address(kernel.getModuleForKeycode(keycode_));
if (moduleForKeycode == address(0)) revert Policy_ModuleDoesNotExist(keycode_);
return moduleForKeycode;
}
/// @notice Define module dependencies for this policy.
/// @return dependencies - Keycode array of module dependencies.
function configureDependencies() external virtual returns (Keycode[] memory dependencies) {}
/// @notice Function called by kernel to set module function permissions.
/// @return requests - Array of keycodes and function selectors for requested permissions.
function requestPermissions() external view virtual returns (Permissions[] memory requests) {}
}
/// @notice Main contract that acts as a central component registry for the protocol.
/// @dev The kernel manages modules and policies. The kernel is mutated via predefined Actions,
/// @dev which are input from any address assigned as the executor. The executor can be changed as needed.
contract Kernel {
// ========= EVENTS ========= //
event PermissionsUpdated(
Keycode indexed keycode_,
Policy indexed policy_,
bytes4 funcSelector_,
bool granted_
);
event ActionExecuted(Actions indexed action_, address indexed target_);
// ========= ERRORS ========= //
error Kernel_OnlyExecutor(address caller_);
error Kernel_ModuleAlreadyInstalled(Keycode module_);
error Kernel_InvalidModuleUpgrade(Keycode module_);
error Kernel_PolicyAlreadyActivated(address policy_);
error Kernel_PolicyNotActivated(address policy_);
// ========= PRIVILEGED ADDRESSES ========= //
/// @notice Address that is able to initiate Actions in the kernel. Can be assigned to a multisig or governance contract.
address public executor;
// ========= MODULE MANAGEMENT ========= //
/// @notice Array of all modules currently installed.
Keycode[] public allKeycodes;
/// @notice Mapping of module address to keycode.
mapping(Keycode => Module) public getModuleForKeycode;
/// @notice Mapping of keycode to module address.
mapping(Module => Keycode) public getKeycodeForModule;
/// @notice Mapping of a keycode to all of its policy dependents. Used to efficiently reconfigure policy dependencies.
mapping(Keycode => Policy[]) public moduleDependents;
/// @notice Helper for module dependent arrays. Prevents the need to loop through array.
mapping(Keycode => mapping(Policy => uint256)) public getDependentIndex;
/// @notice Module <> Policy Permissions.
/// @dev Keycode -> Policy -> Function Selector -> bool for permission
mapping(Keycode => mapping(Policy => mapping(bytes4 => bool))) public modulePermissions;
// ========= POLICY MANAGEMENT ========= //
/// @notice List of all active policies
Policy[] public activePolicies;
/// @notice Helper to get active policy quickly. Prevents need to loop through array.
mapping(Policy => uint256) public getPolicyIndex;
//============================================================================================//
// CORE FUNCTIONS //
//============================================================================================//
constructor() {
executor = msg.sender;
}
/// @notice Modifier to check if caller is the executor.
modifier onlyExecutor() {
if (msg.sender != executor) revert Kernel_OnlyExecutor(msg.sender);
_;
}
function isPolicyActive(Policy policy_) public view returns (bool) {
return activePolicies.length > 0 && activePolicies[getPolicyIndex[policy_]] == policy_;
}
/// @notice Main kernel function. Initiates state changes to kernel depending on Action passed in.
function executeAction(Actions action_, address target_) external onlyExecutor {
if (action_ == Actions.InstallModule) {
ensureContract(target_);
ensureValidKeycode(Module(target_).KEYCODE());
_installModule(Module(target_));
} else if (action_ == Actions.UpgradeModule) {
ensureContract(target_);
ensureValidKeycode(Module(target_).KEYCODE());
_upgradeModule(Module(target_));
} else if (action_ == Actions.ActivatePolicy) {
ensureContract(target_);
_activatePolicy(Policy(target_));
} else if (action_ == Actions.DeactivatePolicy) {
ensureContract(target_);
_deactivatePolicy(Policy(target_));
} else if (action_ == Actions.ChangeExecutor) {
executor = target_;
} else if (action_ == Actions.MigrateKernel) {
ensureContract(target_);
_migrateKernel(Kernel(target_));
}
emit ActionExecuted(action_, target_);
}
function _installModule(Module newModule_) internal {
Keycode keycode = newModule_.KEYCODE();
if (address(getModuleForKeycode[keycode]) != address(0))
revert Kernel_ModuleAlreadyInstalled(keycode);
getModuleForKeycode[keycode] = newModule_;
getKeycodeForModule[newModule_] = keycode;
allKeycodes.push(keycode);
newModule_.INIT();
}
function _upgradeModule(Module newModule_) internal {
Keycode keycode = newModule_.KEYCODE();
Module oldModule = getModuleForKeycode[keycode];
if (address(oldModule) == address(0) || oldModule == newModule_)
revert Kernel_InvalidModuleUpgrade(keycode);
getKeycodeForModule[oldModule] = Keycode.wrap(bytes5(0));
getKeycodeForModule[newModule_] = keycode;
getModuleForKeycode[keycode] = newModule_;
newModule_.INIT();
_reconfigurePolicies(keycode);
}
function _activatePolicy(Policy policy_) internal {
if (isPolicyActive(policy_)) revert Kernel_PolicyAlreadyActivated(address(policy_));
// Add policy to list of active policies
activePolicies.push(policy_);
getPolicyIndex[policy_] = activePolicies.length - 1;
// Record module dependencies
Keycode[] memory dependencies = policy_.configureDependencies();
uint256 depLength = dependencies.length;
for (uint256 i; i < depLength; ) {
Keycode keycode = dependencies[i];
moduleDependents[keycode].push(policy_);
getDependentIndex[keycode][policy_] = moduleDependents[keycode].length - 1;
unchecked {
++i;
}
}
// Grant permissions for policy to access restricted module functions
Permissions[] memory requests = policy_.requestPermissions();
_setPolicyPermissions(policy_, requests, true);
}
function _deactivatePolicy(Policy policy_) internal {
if (!isPolicyActive(policy_)) revert Kernel_PolicyNotActivated(address(policy_));
// Revoke permissions
Permissions[] memory requests = policy_.requestPermissions();
_setPolicyPermissions(policy_, requests, false);
// Remove policy from all policy data structures
uint256 idx = getPolicyIndex[policy_];
Policy lastPolicy = activePolicies[activePolicies.length - 1];
activePolicies[idx] = lastPolicy;
activePolicies.pop();
getPolicyIndex[lastPolicy] = idx;
delete getPolicyIndex[policy_];
// Remove policy from module dependents
_pruneFromDependents(policy_);
}
/// @notice All functionality will move to the new kernel. WARNING: ACTION WILL BRICK THIS KERNEL.
/// @dev New kernel must add in all of the modules and policies via executeAction.
/// @dev NOTE: Data does not get cleared from this kernel.
function _migrateKernel(Kernel newKernel_) internal {
uint256 keycodeLen = allKeycodes.length;
for (uint256 i; i < keycodeLen; ) {
Module module = Module(getModuleForKeycode[allKeycodes[i]]);
module.changeKernel(newKernel_);
unchecked {
++i;
}
}
uint256 policiesLen = activePolicies.length;
for (uint256 j; j < policiesLen; ) {
Policy policy = activePolicies[j];
// Deactivate before changing kernel
policy.changeKernel(newKernel_);
unchecked {
++j;
}
}
}
function _reconfigurePolicies(Keycode keycode_) internal {
Policy[] memory dependents = moduleDependents[keycode_];
uint256 depLength = dependents.length;
for (uint256 i; i < depLength; ) {
dependents[i].configureDependencies();
unchecked {
++i;
}
}
}
function _setPolicyPermissions(
Policy policy_,
Permissions[] memory requests_,
bool grant_
) internal {
uint256 reqLength = requests_.length;
for (uint256 i = 0; i < reqLength; ) {
Permissions memory request = requests_[i];
modulePermissions[request.keycode][policy_][request.funcSelector] = grant_;
emit PermissionsUpdated(request.keycode, policy_, request.funcSelector, grant_);
unchecked {
++i;
}
}
}
function _pruneFromDependents(Policy policy_) internal {
Keycode[] memory dependencies = policy_.configureDependencies();
uint256 depcLength = dependencies.length;
for (uint256 i; i < depcLength; ) {
Keycode keycode = dependencies[i];
Policy[] storage dependents = moduleDependents[keycode];
uint256 origIndex = getDependentIndex[keycode][policy_];
Policy lastPolicy = dependents[dependents.length - 1];
// Swap with last and pop
dependents[origIndex] = lastPolicy;
dependents.pop();
// Record new index and delete deactivated policy index
getDependentIndex[keycode][lastPolicy] = origIndex;
delete getDependentIndex[keycode][policy_];
unchecked {
++i;
}
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
library SafeCast {
error Overflow(uint256 amount);
function encodeUInt128(uint256 amount) internal pure returns (uint128) {
if (amount > type(uint128).max) {
revert Overflow(amount);
}
return uint128(amount);
}
function encodeUInt112(uint256 amount) internal pure returns (uint112) {
if (amount > type(uint112).max) {
revert Overflow(amount);
}
return uint112(amount);
}
function encodeUInt96(uint256 amount) internal pure returns (uint96) {
if (amount > type(uint96).max) {
revert Overflow(amount);
}
return uint96(amount);
}
function encodeUInt32(uint256 amount) internal pure returns (uint32) {
if (amount > type(uint32).max) {
revert Overflow(amount);
}
return uint32(amount);
}
function encodeUInt16(uint256 amount) internal pure returns (uint16) {
if (amount > type(uint16).max) {
revert Overflow(amount);
}
return uint16(amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
{
"compilationTarget": {
"src/periphery/CoolerV2Migrator.sol": "CoolerV2Migrator"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":@addresses/=lib/forge-proposal-simulator/addresses/",
":@examples/=lib/forge-proposal-simulator/examples/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@proposals/=lib/forge-proposal-simulator/proposals/",
":@script/=lib/forge-proposal-simulator/script/",
":@test/=lib/forge-proposal-simulator/test/",
":@utils/=lib/forge-proposal-simulator/utils/",
":balancer-v2/=lib/balancer-v2/",
":bonds/=lib/bonds/src/",
":clones-with-immutable-args/=lib/clones-with-immutable-args/src/",
":clones/=lib/clones-with-immutable-args/src/",
":comp-governance/=lib/forge-proposal-simulator/lib/compound-governance/contracts/",
":compound-governance/=lib/forge-proposal-simulator/lib/compound-governance/contracts/",
":ds-test/=lib/ds-test/src/",
":erc4626-tests/=lib/forge-proposal-simulator/lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-proposal-simulator/=lib/forge-proposal-simulator/",
":forge-std/=lib/forge-std/src/",
":interfaces/=src/interfaces/",
":layer-zero/=lib/solidity-examples/contracts/",
":libraries/=src/libraries/",
":modules/=src/modules/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/forge-proposal-simulator/lib/openzeppelin-contracts/contracts/",
":policies/=src/policies/",
":proposal-sim/=lib/forge-proposal-simulator/",
":solidity-code-metrics/=node_modules/solidity-code-metrics/",
":solidity-examples/=lib/solidity-examples/contracts/",
":solmate/=lib/solmate/src/",
":surl-1.0.0/=dependencies/surl-1.0.0/src/",
":test/=src/test/",
":test/lib/=src/test/lib/",
":test/mocks/=src/test/mocks/",
"lib/forge-proposal-simulator:Governors/=lib/forge-proposal-simulator/Governors/"
]
}
[{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"coolerV2_","type":"address"},{"internalType":"address","name":"dai_","type":"address"},{"internalType":"address","name":"usds_","type":"address"},{"internalType":"address","name":"gohm_","type":"address"},{"internalType":"address","name":"migrator_","type":"address"},{"internalType":"address","name":"flash_","type":"address"},{"internalType":"address","name":"chreg_","type":"address"},{"internalType":"address[]","name":"coolerFactories_","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NotDisabled","type":"error"},{"inputs":[],"name":"NotEnabled","type":"error"},{"inputs":[],"name":"OnlyLender","type":"error"},{"inputs":[],"name":"OnlyThis","type":"error"},{"inputs":[],"name":"Only_CoolerOwner","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Overflow","type":"error"},{"inputs":[],"name":"Params_DuplicateCooler","type":"error"},{"inputs":[{"internalType":"string","name":"reason_","type":"string"}],"name":"Params_InvalidAddress","type":"error"},{"inputs":[],"name":"Params_InvalidClearinghouse","type":"error"},{"inputs":[],"name":"Params_InvalidCooler","type":"error"},{"inputs":[],"name":"Params_InvalidNewOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"coolerFactory","type":"address"}],"name":"CoolerFactoryAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"coolerFactory","type":"address"}],"name":"CoolerFactoryRemoved","type":"event"},{"anonymous":false,"inputs":[],"name":"Disabled","type":"event"},{"anonymous":false,"inputs":[],"name":"Enabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokenRefunded","type":"event"},{"inputs":[],"name":"CHREG","outputs":[{"internalType":"contract CHREGv1","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"COOLERV2","outputs":[{"internalType":"contract IMonoCooler","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DAI","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FLASH","outputs":[{"internalType":"contract IERC3156FlashLender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GOHM","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIGRATOR","outputs":[{"internalType":"contract IDaiUsdsMigrator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"USDS","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coolerFactory_","type":"address"}],"name":"addCoolerFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"coolers_","type":"address[]"},{"internalType":"address","name":"newOwner_","type":"address"},{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"authorized","type":"address"},{"internalType":"uint96","name":"authorizationDeadline","type":"uint96"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"signatureDeadline","type":"uint256"}],"internalType":"struct IMonoCooler.Authorization","name":"authorization_","type":"tuple"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct IMonoCooler.Signature","name":"signature_","type":"tuple"},{"components":[{"internalType":"address","name":"delegate","type":"address"},{"internalType":"int256","name":"amount","type":"int256"}],"internalType":"struct IDLGTEv1.DelegationRequest[]","name":"delegationRequests_","type":"tuple[]"}],"name":"consolidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"disable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"enable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCoolerFactories","outputs":[{"internalType":"address[]","name":"coolerFactories","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"initiator_","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"params_","type":"bytes"}],"name":"onFlashLoan","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"coolers_","type":"address[]"}],"name":"previewConsolidate","outputs":[{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"uint256","name":"borrowAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coolerFactory_","type":"address"}],"name":"removeCoolerFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]