More Info
Private Name Tags
ContractCreator
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Delegate Voting | 18866662 | 354 days ago | IN | 0 ETH | 0.00147511 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|
18234541 | 442 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Minimal Proxy Contract for 0xe504ca8a954a757f79a3728e7985f144860af41e
Contract Name:
Cooler
Compiler Version
v0.8.15+commit.e14f2714
Optimization Enabled:
Yes with 10 runs
Other Settings:
london EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// 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: 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"); } }
// 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: 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: 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: 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: 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(); } } } }
{ "remappings": [ "ds-test/=lib/ds-test/src/", "forge-std/=lib/forge-std/src/", "interfaces/=src/interfaces/", "modules/=src/modules/", "policies/=src/policies/", "libraries/=src/libraries/", "solmate/=lib/solmate/src/", "layer-zero/=lib/solidity-examples/contracts/", "bonds/=lib/bonds/src/", "test/=src/test/", "clones/=lib/clones-with-immutable-args/src/", "cooler/=lib/Cooler/src/", "balancer-v2/=lib/balancer-v2/", "@openzeppelin/=lib/openzeppelin-contracts/", "Cooler/=lib/Cooler/src/", "clones-with-immutable-args/=lib/clones-with-immutable-args/src/", "olympus-v3/=lib/Cooler/lib/olympus-v3/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "solidity-examples/=lib/solidity-examples/contracts/" ], "optimizer": { "enabled": true, "runs": 10 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs" }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "london", "libraries": {} }
[{"inputs":[],"name":"Deactivated","type":"error"},{"inputs":[],"name":"Default","type":"error"},{"inputs":[],"name":"NotCoolerCallback","type":"error"},{"inputs":[],"name":"NotExpired","type":"error"},{"inputs":[],"name":"OnlyApproved","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"approvals","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"approveTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"claimDefaulted","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"},{"internalType":"address","name":"recipient_","type":"address"},{"internalType":"bool","name":"isCallback_","type":"bool"}],"name":"clearRequest","outputs":[{"internalType":"uint256","name":"loanID","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"contract ERC20","name":"_collateral","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal_","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral_","type":"uint256"}],"name":"collateralFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debt","outputs":[{"internalType":"contract ERC20","name":"_debt","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"}],"name":"delegateVoting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"uint8","name":"times_","type":"uint8"}],"name":"extendLoanTerms","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract CoolerFactory","name":"_factory","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"getLoan","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"request","type":"tuple"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"interestDue","type":"uint256"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"callback","type":"bool"}],"internalType":"struct Cooler.Loan","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"getRequest","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"hasExpired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal_","type":"uint256"},{"internalType":"uint256","name":"rate_","type":"uint256"},{"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"interestFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"loans","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"request","type":"tuple"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"interestDue","type":"uint256"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"callback","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"uint256","name":"repayment_","type":"uint256"}],"name":"repayLoan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"interest_","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral_","type":"uint256"},{"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"requestLoan","outputs":[{"internalType":"uint256","name":"reqID","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requests","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"rescindRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"address","name":"recipient_","type":"address"}],"name":"setRepaymentAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | 100.00% | $5,532.79 | 821.2889 | $4,544,018.86 |
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.