Contract Name:
WeightedRateSetCollectionPool
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/extensions/IERC20Metadata.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../token/ERC721/extensions/IERC721Metadata.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Create2.sol)
pragma solidity ^0.8.0;
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
require(address(this).balance >= amount, "Create2: insufficient balance");
require(bytecode.length != 0, "Create2: bytecode length is zero");
/// @solidity memory-safe-assembly
assembly {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
}
require(addr != address(0), "Create2: Failed on deploy");
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := keccak256(start, 85)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toUint248(uint256 value) internal pure returns (uint248) {
require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toUint240(uint256 value) internal pure returns (uint240) {
require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toUint232(uint256 value) internal pure returns (uint232) {
require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.2._
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toUint216(uint256 value) internal pure returns (uint216) {
require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toUint208(uint256 value) internal pure returns (uint208) {
require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toUint200(uint256 value) internal pure returns (uint200) {
require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toUint192(uint256 value) internal pure returns (uint192) {
require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toUint184(uint256 value) internal pure returns (uint184) {
require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toUint176(uint256 value) internal pure returns (uint176) {
require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toUint168(uint256 value) internal pure returns (uint168) {
require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toUint160(uint256 value) internal pure returns (uint160) {
require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toUint152(uint256 value) internal pure returns (uint152) {
require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toUint144(uint256 value) internal pure returns (uint144) {
require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toUint136(uint256 value) internal pure returns (uint136) {
require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v2.5._
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toUint120(uint256 value) internal pure returns (uint120) {
require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toUint112(uint256 value) internal pure returns (uint112) {
require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.2._
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toUint80(uint256 value) internal pure returns (uint80) {
require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toUint72(uint256 value) internal pure returns (uint72) {
require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v2.5._
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toUint56(uint256 value) internal pure returns (uint56) {
require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toUint48(uint256 value) internal pure returns (uint48) {
require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toUint40(uint256 value) internal pure returns (uint40) {
require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v2.5._
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toUint24(uint256 value) internal pure returns (uint24) {
require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v2.5._
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*
* _Available since v2.5._
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*
* _Available since v3.0._
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.7._
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.7._
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*
* _Available since v3.0._
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol)
pragma solidity ^0.8.0;
import "./Address.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/
abstract contract Multicall {
/**
* @dev Receives and executes a batch of function calls on this contract.
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), data[i]);
}
return results;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./Pool.sol";
import "./LoanReceipt.sol";
import "./LiquidityLogic.sol";
import "./interfaces/IPool.sol";
import "./integrations/DelegateCash/IDelegateRegistryV1.sol";
import "./integrations/DelegateCash/IDelegateRegistryV2.sol";
/**
* @title Borrow Logic
* @author MetaStreet Labs
*/
library BorrowLogic {
using SafeCast for uint256;
using LiquidityLogic for LiquidityLogic.Liquidity;
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Borrow options tag size in bytes
*/
uint256 internal constant BORROW_OPTIONS_TAG_SIZE = 2;
/**
* @notice Borrow options length size in bytes
*/
uint256 internal constant BORROW_OPTIONS_LENGTH_SIZE = 2;
/**************************************************************************/
/* Helpers */
/**************************************************************************/
/**
* @notice Helper function to extract specified option tag from options
* data
*
* @dev Options are encoded as:
* 2 byte uint16 tag
* 2 byte uint16 length
* n byte bytes data
* The first matching tag is returned.
*
* @param options Encoded options
* @param tag Tag to find
* @return Options data
*/
function _getOptionsData(bytes calldata options, Pool.BorrowOptions tag) internal pure returns (bytes calldata) {
/* Scan the options for the tag */
for (uint256 offsetTag; offsetTag < options.length; ) {
/* Compute offsets with for tag length and data */
uint256 offsetLength = offsetTag + BORROW_OPTIONS_TAG_SIZE;
uint256 offsetData = offsetTag + BORROW_OPTIONS_TAG_SIZE + BORROW_OPTIONS_LENGTH_SIZE;
/* The tag is in the first 2 bytes of each options item */
uint256 currentTag = uint16(bytes2(options[offsetTag:offsetLength]));
/* The length of the options data is in the second 2 bytes of each options item, after the tag */
uint256 dataLength = uint16(bytes2(options[offsetLength:offsetData]));
/* Return the offset and length if the tag is found */
if (currentTag == uint256(tag)) {
return options[offsetData:offsetData + dataLength];
}
/* Increment to next options item */
offsetTag = offsetData + dataLength;
}
/* Return empty slice if no tag is found */
return options[0:0];
}
/**
* @notice Helper function that calls delegate.cash registry to delegate token
*
* @param delegations Delegate storage
* @param collateralToken Collateral token
* @param collateralTokenId Collateral token ID
* @param delegateRegistryV1 Delegate registry v1 address
* @param delegateRegistryV2 Delegate registry v2 address
* @param options Options data
*/
function _optionDelegateCash(
Pool.DelegateStorage storage delegations,
address collateralToken,
uint256 collateralTokenId,
address delegateRegistryV1,
address delegateRegistryV2,
bytes calldata options
) external {
/* Find delegate.cash v2 tagged data in options */
bytes calldata delegateDataV2 = _getOptionsData(options, Pool.BorrowOptions.DelegateCashV2);
if (delegateDataV2.length != 0) {
if (delegateRegistryV2 == address(0)) revert IPool.InvalidBorrowOptions();
if (delegateDataV2.length != 20) revert IPool.InvalidBorrowOptions();
/* Store delegate in mapping */
delegations.delegates[collateralToken][collateralTokenId] = Pool.Delegate({
version: Pool.DelegateVersion.DelegateCashV2,
to: address(uint160(bytes20(delegateDataV2)))
});
/* Delegate token */
IDelegateRegistryV2(delegateRegistryV2).delegateERC721(
address(uint160(bytes20(delegateDataV2))),
collateralToken,
collateralTokenId,
"",
true
);
/* Return if found, skip additional search */
return;
}
/* Find delegate.cash v1 tagged data in options, if v2 data is empty */
bytes calldata delegateDataV1 = _getOptionsData(options, Pool.BorrowOptions.DelegateCashV1);
if (delegateDataV1.length != 0) {
if (delegateRegistryV1 == address(0)) revert IPool.InvalidBorrowOptions();
if (delegateDataV1.length != 20) revert IPool.InvalidBorrowOptions();
/* Store delegate in mapping */
delegations.delegates[collateralToken][collateralTokenId] = Pool.Delegate({
version: Pool.DelegateVersion.DelegateCashV1,
to: address(uint160(bytes20(delegateDataV1)))
});
/* Delegate token */
IDelegateRegistryV1(delegateRegistryV1).delegateForToken(
address(uint160(bytes20(delegateDataV1))),
collateralToken,
collateralTokenId,
true
);
}
}
/**
* @notice Helper function to revoke token delegate
*
* @param delegations Delegate storage
* @param collateralToken Contract address of token that delegation is being removed from
* @param collateralTokenId Token id of token that delegation is being removed from
* @param delegateRegistryV1 Delegate registry v1 address
* @param delegateRegistryV2 Delegate registry v2 address
*/
function _revokeDelegates(
Pool.DelegateStorage storage delegations,
address collateralToken,
uint256 collateralTokenId,
address delegateRegistryV1,
address delegateRegistryV2
) external {
Pool.Delegate memory delegate = delegations.delegates[collateralToken][collateralTokenId];
if (delegate.version == Pool.DelegateVersion.None) {
return;
} else if (delegate.version == Pool.DelegateVersion.DelegateCashV2) {
IDelegateRegistryV2(delegateRegistryV2).delegateERC721(
delegate.to,
collateralToken,
collateralTokenId,
"",
false
);
} else if (delegate.version == Pool.DelegateVersion.DelegateCashV1) {
IDelegateRegistryV1(delegateRegistryV1).delegateForToken(
delegate.to,
collateralToken,
collateralTokenId,
false
);
}
/* Remove delegate from mapping */
delete delegations.delegates[collateralToken][collateralTokenId];
}
/**
* @dev Helper function to calculated prorated repayment
* @param loanReceipt Decoded loan receipt
* @return repayment amount in currency tokens
* @return proration based on elapsed duration
*/
function _prorateRepayment(
LoanReceipt.LoanReceiptV2 memory loanReceipt
) internal view returns (uint256 repayment, uint256 proration) {
/* Minimum of proration and 1.0 */
proration = Math.min(
((block.timestamp - (loanReceipt.maturity - loanReceipt.duration)) * LiquidityLogic.FIXED_POINT_SCALE) /
loanReceipt.duration,
LiquidityLogic.FIXED_POINT_SCALE
);
/* Compute repayment using prorated interest */
repayment =
loanReceipt.principal +
(((loanReceipt.repayment - loanReceipt.principal) * proration) / LiquidityLogic.FIXED_POINT_SCALE);
}
/**
* @dev Helper function to decode a loan receipt
* @param loanReceipt Loan receipt
* @return Decoded loan receipt
*/
function _decodeLoanReceipt(bytes calldata loanReceipt) external pure returns (LoanReceipt.LoanReceiptV2 memory) {
return LoanReceipt.decode(loanReceipt);
}
/**
* @dev Helper function to handle borrow accounting
* @param self Pool storage
* @param principal Principal amount in currency tokens
* @param duration Duration in seconds
* @param collateralToken Collateral token address
* @param collateralTokenId Collateral token ID
* @param repayment Repayment amount in currency tokens
* @param maxRepayment Maximum repayment amount in currency tokens
* @param adminFee Admin fee
* @param nodes Liquidity nodes
* @param count Liquidity nodes count
* @param collateralWrapperContext Collateral wrapper context data
* @return Encoded loan receipt, loan receipt hash
*/
function _borrow(
Pool.PoolStorage storage self,
uint256 principal,
uint64 duration,
address collateralToken,
uint256 collateralTokenId,
uint256 repayment,
uint256 maxRepayment,
uint256 adminFee,
LiquidityLogic.NodeSource[] memory nodes,
uint16 count,
bytes memory collateralWrapperContext
) external returns (bytes memory, bytes32) {
/* Validate principal is non-zero */
if (principal == 0) revert IPool.InvalidParameters();
/* Validate duration is non-zero */
if (duration == 0) revert IPool.UnsupportedLoanDuration();
/* Validate repayment */
if (repayment > maxRepayment) revert IPool.RepaymentTooHigh();
/* Build the loan receipt */
LoanReceipt.LoanReceiptV2 memory receipt = LoanReceipt.LoanReceiptV2({
version: 2,
principal: principal,
repayment: repayment,
adminFee: adminFee,
borrower: msg.sender,
maturity: (block.timestamp + duration).toUint64(),
duration: duration,
collateralToken: collateralToken,
collateralTokenId: collateralTokenId,
collateralWrapperContextLen: collateralWrapperContext.length.toUint16(),
collateralWrapperContext: collateralWrapperContext,
nodeReceipts: new LoanReceipt.NodeReceipt[](count)
});
/* Use liquidity nodes */
for (uint256 i; i < count; i++) {
/* Use node */
self.liquidity.use(nodes[i].tick, nodes[i].used, nodes[i].pending, duration);
/* Construct node receipt */
receipt.nodeReceipts[i] = LoanReceipt.NodeReceipt({
tick: nodes[i].tick,
used: nodes[i].used,
pending: nodes[i].pending
});
}
/* Encode and hash the loan receipt */
bytes memory encodedLoanReceipt = LoanReceipt.encode(receipt);
bytes32 loanReceiptHash = LoanReceipt.hash(encodedLoanReceipt);
/* Validate no loan receipt hash collision */
if (self.loans[loanReceiptHash] != Pool.LoanStatus.Uninitialized) revert IPool.InvalidLoanReceipt();
/* Store loan status */
self.loans[loanReceiptHash] = Pool.LoanStatus.Active;
return (encodedLoanReceipt, loanReceiptHash);
}
/**
* @dev Helper function to handle repay accounting
* @param self Pool storage
* @param encodedLoanReceipt Encoded loan receipt
* @return Repayment amount in currency tokens, decoded loan receipt, loan
* receipt hash
*/
function _repay(
Pool.PoolStorage storage self,
bytes calldata encodedLoanReceipt
) external returns (uint256, LoanReceipt.LoanReceiptV2 memory, bytes32) {
/* Compute loan receipt hash */
bytes32 loanReceiptHash = LoanReceipt.hash(encodedLoanReceipt);
/* Validate loan receipt */
if (self.loans[loanReceiptHash] != Pool.LoanStatus.Active) revert IPool.InvalidLoanReceipt();
/* Decode loan receipt */
LoanReceipt.LoanReceiptV2 memory loanReceipt = LoanReceipt.decode(encodedLoanReceipt);
/* Validate borrow and repay is not in same block */
if (loanReceipt.maturity - loanReceipt.duration == block.timestamp) revert IPool.InvalidLoanReceipt();
/* Validate caller is borrower */
if (msg.sender != loanReceipt.borrower) revert IPool.InvalidCaller();
/* Compute proration and repayment using prorated interest */
(uint256 repayment, uint256 proration) = _prorateRepayment(loanReceipt);
/* Compute elapsed time since loan origination */
uint64 elapsed = uint64(block.timestamp + loanReceipt.duration - loanReceipt.maturity);
/* Restore liquidity nodes */
for (uint256 i; i < loanReceipt.nodeReceipts.length; i++) {
/* Restore node */
self.liquidity.restore(
loanReceipt.nodeReceipts[i].tick,
loanReceipt.nodeReceipts[i].used,
loanReceipt.nodeReceipts[i].pending,
loanReceipt.nodeReceipts[i].used +
uint128(
((loanReceipt.nodeReceipts[i].pending - loanReceipt.nodeReceipts[i].used) * proration) /
LiquidityLogic.FIXED_POINT_SCALE
),
loanReceipt.duration,
elapsed
);
}
/* Update admin fee total balance with prorated admin fee */
self.adminFeeBalance += (loanReceipt.adminFee * proration) / LiquidityLogic.FIXED_POINT_SCALE;
/* Mark loan status repaid */
self.loans[loanReceiptHash] = Pool.LoanStatus.Repaid;
return (repayment, loanReceipt, loanReceiptHash);
}
/**
* @dev Helper function to handle liquidate accounting
* @param self Pool storage
* @param encodedLoanReceipt Encoded loan receipt
* @return Decoded loan receipt, loan receipt hash
*/
function _liquidate(
Pool.PoolStorage storage self,
bytes calldata encodedLoanReceipt
) external returns (LoanReceipt.LoanReceiptV2 memory, bytes32) {
/* Compute loan receipt hash */
bytes32 loanReceiptHash = LoanReceipt.hash(encodedLoanReceipt);
/* Validate loan status is active */
if (self.loans[loanReceiptHash] != Pool.LoanStatus.Active) revert IPool.InvalidLoanReceipt();
/* Decode loan receipt */
LoanReceipt.LoanReceiptV2 memory loanReceipt = LoanReceipt.decode(encodedLoanReceipt);
/* Validate loan is expired */
if (block.timestamp <= loanReceipt.maturity) revert IPool.LoanNotExpired();
/* Mark loan status liquidated */
self.loans[loanReceiptHash] = Pool.LoanStatus.Liquidated;
return (loanReceipt, loanReceiptHash);
}
/**
* @dev Helper function to handle collateral liquidation accounting
* @param self Pool storage
* @param encodedLoanReceipt Encoded loan receipt
* @param proceeds Proceeds amount in currency tokens
* @return Borrower surplus, decoded loan receipt, loan receipt hash
*/
function _onCollateralLiquidated(
Pool.PoolStorage storage self,
bytes calldata encodedLoanReceipt,
uint256 proceeds
) external returns (uint256, LoanReceipt.LoanReceiptV2 memory, bytes32) {
/* Compute loan receipt hash */
bytes32 loanReceiptHash = LoanReceipt.hash(encodedLoanReceipt);
/* Validate loan status is liquidated */
if (self.loans[loanReceiptHash] != Pool.LoanStatus.Liquidated) revert IPool.InvalidLoanReceipt();
/* Decode loan receipt */
LoanReceipt.LoanReceiptV2 memory loanReceipt = LoanReceipt.decode(encodedLoanReceipt);
/* Compute borrower's share of liquidation surplus */
uint256 borrowerSurplus = proceeds > loanReceipt.repayment ? proceeds - loanReceipt.repayment : 0;
/* Compute elapsed time since loan origination */
uint64 elapsed = uint64(block.timestamp + loanReceipt.duration - loanReceipt.maturity);
/* Restore liquidity nodes */
uint256 proceedsRemaining = proceeds - borrowerSurplus;
uint256 lastIndex = loanReceipt.nodeReceipts.length - 1;
for (uint256 i; i < loanReceipt.nodeReceipts.length; i++) {
/* Compute amount to restore depending on whether there is a surplus */
uint256 restored = (i == lastIndex)
? proceedsRemaining
: Math.min(loanReceipt.nodeReceipts[i].pending, proceedsRemaining);
/* Restore node */
self.liquidity.restore(
loanReceipt.nodeReceipts[i].tick,
loanReceipt.nodeReceipts[i].used,
loanReceipt.nodeReceipts[i].pending,
restored.toUint128(),
loanReceipt.duration,
elapsed
);
/* Update proceeds remaining */
proceedsRemaining -= restored;
}
/* Mark loan status collateral liquidated */
self.loans[loanReceiptHash] = Pool.LoanStatus.CollateralLiquidated;
return (borrowerSurplus, loanReceipt, loanReceiptHash);
}
/**
* @dev Helper function to set admin fee rate
* @param self Pool storage
* @param rate Rate is the admin fee in basis points
*/
function _setAdminFeeRate(Pool.PoolStorage storage self, uint32 rate) external {
if (msg.sender != self.admin) revert IPool.InvalidCaller();
if (rate >= LiquidityLogic.BASIS_POINTS_SCALE) revert IPool.InvalidParameters();
self.adminFeeRate = rate;
}
/**
* @dev Helper function to withdraw admin fees
* @param self Pool storage
* @param recipient Recipient account
* @param scaledAmount Amount to withdraw
*/
function _withdrawAdminFees(Pool.PoolStorage storage self, address recipient, uint256 scaledAmount) external {
if (msg.sender != self.admin) revert IPool.InvalidCaller();
if (recipient == address(0) || scaledAmount > self.adminFeeBalance) revert IPool.InvalidParameters();
/* Update admin fees balance */
self.adminFeeBalance -= scaledAmount;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../Pool.sol";
import "../rates/WeightedInterestRateModel.sol";
import "../filters/SetCollectionCollateralFilter.sol";
import "../tokenization/ERC20DepositToken.sol";
import "../oracle/ExternalPriceOracle.sol";
/**
* @title Pool Configuration with a Weighted Interest Rate Model and Set Collection
* Collateral Filter
* @author MetaStreet Labs
*/
contract WeightedRateSetCollectionPool is
Pool,
WeightedInterestRateModel,
SetCollectionCollateralFilter,
ERC20DepositToken,
ExternalPriceOracle
{
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice Pool constructor
* @param collateralLiquidator Collateral liquidator
* @param delegateRegistryV1 Delegation registry v1 contract
* @param delegateRegistryV2 Delegation registry v2 contract
* @param erc20DepositTokenImplementation ERC20 Deposit Token implementation address
* @param collateralWrappers Collateral wrappers
*/
constructor(
address collateralLiquidator,
address delegateRegistryV1,
address delegateRegistryV2,
address erc20DepositTokenImplementation,
address[] memory collateralWrappers
)
Pool(collateralLiquidator, delegateRegistryV1, delegateRegistryV2, collateralWrappers)
WeightedInterestRateModel()
ERC20DepositToken(erc20DepositTokenImplementation)
ExternalPriceOracle()
{
/* Disable initialization of implementation contract */
_storage.currencyToken = IERC20(address(1));
}
/**************************************************************************/
/* Initializer */
/**************************************************************************/
/**
* @notice Initializer
* @dev Fee-on-transfer currency tokens are not supported
* @param params ABI-encoded parameters
*/
function initialize(bytes memory params) external {
require(address(_storage.currencyToken) == address(0), "Already initialized");
/* Decode parameters */
(
address collateralToken_,
uint256[] memory tokenIds_,
address currencyToken_,
address priceOracle_,
uint64[] memory durations_,
uint64[] memory rates_
) = abi.decode(params, (address, uint256[], address, address, uint64[], uint64[]));
/* Initialize Collateral Filter */
SetCollectionCollateralFilter._initialize(collateralToken_, tokenIds_);
/* Initialize External Price Oracle */
ExternalPriceOracle.__initialize(priceOracle_);
/* Initialize Pool */
Pool._initialize(currencyToken_, durations_, rates_);
}
/**************************************************************************/
/* Name */
/**************************************************************************/
/**
* @inheritdoc Pool
*/
function IMPLEMENTATION_NAME() external pure override returns (string memory) {
return "WeightedRateSetCollectionPool";
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "./Pool.sol";
import "./Tick.sol";
import "./LiquidityLogic.sol";
import "./interfaces/IPool.sol";
/**
* @title Deposit Logic
* @author MetaStreet Labs
*/
library DepositLogic {
using LiquidityLogic for LiquidityLogic.Liquidity;
/**
* @dev Helper function to handle deposit accounting
* @param self Pool storage
* @param tick Tick
* @param amount Amount
* @param minShares Minimum shares
* @return Deposit shares
*/
function _deposit(
Pool.PoolStorage storage self,
uint128 tick,
uint128 amount,
uint128 minShares
) external returns (uint128) {
/* Validate tick */
Tick.validate(tick, 0, 0, self.durations.length - 1, 0, self.rates.length - 1);
/* Deposit into liquidity node */
uint128 shares = self.liquidity.deposit(tick, amount);
/* Validate shares received is sufficient */
if (shares == 0 || shares < minShares) revert IPool.InsufficientShares();
/* Add to deposit */
self.deposits[msg.sender][tick].shares += shares;
return shares;
}
/**
* @dev Helper function to handle redeem accounting
* @param self Pool storage
* @param tick Tick
* @param shares Shares
* @return redemptionId Redemption ID
*/
function _redeem(Pool.PoolStorage storage self, uint128 tick, uint128 shares) external returns (uint128) {
/* Look up deposit */
Pool.Deposit storage dep = self.deposits[msg.sender][tick];
/* Assign redemption ID */
uint128 redemptionId = dep.redemptionId++;
/* Look up redemption */
Pool.Redemption storage redemption = dep.redemptions[redemptionId];
/* Validate shares */
if (shares == 0 || shares > dep.shares) revert IPool.InsufficientShares();
/* Redeem shares in tick with liquidity manager */
(uint128 index, uint128 target) = self.liquidity.redeem(tick, shares);
/* Update deposit state */
redemption.pending = shares;
redemption.index = index;
redemption.target = target;
/* Decrement deposit shares */
dep.shares -= shares;
return redemptionId;
}
/**
* @dev Helper function to handle withdraw accounting
* @param self Pool storage
* @param tick Tick
* @param redemptionId Redemption ID
* @return Withdrawn shares and withdrawn amount
*/
function _withdraw(
Pool.PoolStorage storage self,
uint128 tick,
uint128 redemptionId
) external returns (uint128, uint128) {
/* Look up redemption */
Pool.Redemption storage redemption = self.deposits[msg.sender][tick].redemptions[redemptionId];
/* If no redemption is pending */
if (redemption.pending == 0) revert IPool.InvalidRedemptionStatus();
/* Look up redemption available */
(uint128 shares, uint128 amount, uint128 processedIndices, uint128 processedShares) = self
.liquidity
.redemptionAvailable(tick, redemption.pending, redemption.index, redemption.target);
/* If the entire redemption is ready */
if (shares == redemption.pending) {
delete self.deposits[msg.sender][tick].redemptions[redemptionId];
} else {
redemption.pending -= shares;
redemption.index += processedIndices;
redemption.target = (processedShares < redemption.target) ? redemption.target - processedShares : 0;
}
return (shares, amount);
}
/**
* @dev Helper function to handle transfer accounting
* @param self Pool storage
* @param from From
* @param to To
* @param tick Tick
* @param shares Shares
*/
function _transfer(Pool.PoolStorage storage self, address from, address to, uint128 tick, uint128 shares) external {
if (self.deposits[from][tick].shares < shares) revert IPool.InsufficientShares();
self.deposits[from][tick].shares -= shares;
self.deposits[to][tick].shares += shares;
}
/**
* Helper function to look up redemption available
* @param self Pool storage
* @param account Account
* @param tick Tick
* @param redemptionId Redemption ID
* @return shares Amount of deposit shares available for redemption
* @return amount Amount of currency tokens available for withdrawal
* @return sharesAhead Amount of pending shares ahead in queue
*/
function _redemptionAvailable(
Pool.PoolStorage storage self,
address account,
uint128 tick,
uint128 redemptionId
) external view returns (uint256 shares, uint256 amount, uint256 sharesAhead) {
/* Look up redemption */
Pool.Redemption storage redemption = self.deposits[account][tick].redemptions[redemptionId];
/* If no redemption is pending */
if (redemption.pending == 0) return (0, 0, 0);
uint128 processedShares;
(shares, amount, , processedShares) = self.liquidity.redemptionAvailable(
tick,
redemption.pending,
redemption.index,
redemption.target
);
/* Compute pending shares ahead in queue */
sharesAhead = redemption.target > processedShares ? redemption.target - processedShares : 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Collateral Filter API
* @author MetaStreet Labs
*/
abstract contract CollateralFilter {
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Invalid parameters
*/
error InvalidCollateralFilterParameters();
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get collateral filter name
* @return Collateral filter name
*/
function COLLATERAL_FILTER_NAME() external view virtual returns (string memory);
/**
* @notice Get collateral filter version
* @return Collateral filter version
*/
function COLLATERAL_FILTER_VERSION() external view virtual returns (string memory);
/**
* @notice Get collateral token
* @return Collateral token contract
*/
function collateralToken() public view virtual returns (address);
/**
* @notice Get collateral tokens
* @return Collateral token contract
*/
function collateralTokens() external view virtual returns (address[] memory);
/**
* Query if collateral token is supported
* @param token Collateral token contract
* @param tokenId Collateral Token ID
* @param index Collateral Token ID index
* @param context ABI-encoded context
* @return True if supported, otherwise false
*/
function _collateralSupported(
address token,
uint256 tokenId,
uint256 index,
bytes calldata context
) internal view virtual returns (bool);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./CollateralFilter.sol";
/**
* @title Set Collection Collateral Filter
* @author MetaStreet Labs
*/
contract SetCollectionCollateralFilter is CollateralFilter {
using EnumerableSet for EnumerableSet.UintSet;
/**************************************************************************/
/* State */
/**************************************************************************/
/**
* @notice Supported token
*/
address private _token;
/**
* @notice Set of supported token IDs
*/
EnumerableSet.UintSet private _tokenIds;
/**************************************************************************/
/* Initializer */
/**************************************************************************/
/**
* @notice SetCollectionCollateralFilter initializer
*/
function _initialize(address token, uint256[] memory tokenIds_) internal {
/* Validate root and node count */
if (tokenIds_.length == 0) revert InvalidCollateralFilterParameters();
/* Set supported token */
_token = token;
/* Add each token ID to set of token IDs */
for (uint256 i; i < tokenIds_.length; i++) {
_tokenIds.add(tokenIds_[i]);
}
}
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* @inheritdoc CollateralFilter
*/
function COLLATERAL_FILTER_NAME() external pure override returns (string memory) {
return "SetCollectionCollateralFilter";
}
/**
* @inheritdoc CollateralFilter
*/
function COLLATERAL_FILTER_VERSION() external pure override returns (string memory) {
return "1.0";
}
/**
* @notice Get collateral token
* @return Collateral token contract
*/
function collateralToken() public view override returns (address) {
return _token;
}
/**
* @inheritdoc CollateralFilter
*/
function collateralTokens() external view override returns (address[] memory) {
address[] memory tokens = new address[](1);
tokens[0] = _token;
return tokens;
}
/**
* @notice Get collateral token IDs
* @return Collateral token IDs
*/
function collateralTokenIds() external view returns (uint256[] memory) {
return _tokenIds.values();
}
/**************************************************************************/
/* Implementation */
/**************************************************************************/
/**
* @inheritdoc CollateralFilter
*/
function _collateralSupported(
address token,
uint256 tokenId,
uint256,
bytes calldata
) internal view override returns (bool) {
/* Validate token supported */
if (token != _token) return false;
/* Validate token ID is in set of token IDs */
return _tokenIds.contains(tokenId);
}
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.19;
/**
* @title IDelegateRegistryV1
*
* @dev Subset of full interface
*/
interface IDelegateRegistryV1 {
/// @notice Delegation type
enum DelegationType {
NONE,
ALL,
CONTRACT,
TOKEN
}
/// @notice Info about a single delegation, used for onchain enumeration
struct DelegationInfo {
DelegationType type_;
address vault;
address delegate;
address contract_;
uint256 tokenId;
}
/// @notice Info about a single contract-level delegation
struct ContractDelegation {
address contract_;
address delegate;
}
/// @notice Info about a single token-level delegation
struct TokenDelegation {
address contract_;
uint256 tokenId;
address delegate;
}
/// @notice Emitted when a user delegates a specific token
event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value);
/**
* ----------- WRITE -----------
*/
/**
* @notice Allow the delegate to act on your behalf for a specific token
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param tokenId The token id for the token you're delegating
* @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
*/
function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external;
/**
* ----------- READ -----------
*/
/**
* @notice Returns an array of contract-level delegates for a given vault's token
* @param vault The cold wallet who issued the delegation
* @param contract_ The address for the contract holding the token
* @param tokenId The token id for the token you're delegating
* @return addresses Array of contract-level delegates for a given vault's token
*/
function getDelegatesForToken(
address vault,
address contract_,
uint256 tokenId
) external view returns (address[] memory);
/**
* @notice Returns true if the address is delegated to act on the entire vault
* @param delegate The hotwallet to act on your behalf
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForAll(address delegate, address vault) external view returns (bool);
/**
* @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForContract(address delegate, address vault, address contract_) external view returns (bool);
/**
* @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param tokenId The token id for the token you're delegating
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForToken(
address delegate,
address vault,
address contract_,
uint256 tokenId
) external view returns (bool);
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;
/**
* @title IDelegateRegistryV2
*
* @dev Subset of full interface
*
* @author foobar (0xfoobar)
*/
interface IDelegateRegistryV2 {
/// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
enum DelegationType {
NONE,
ALL,
CONTRACT,
ERC721,
ERC20,
ERC1155
}
/// @notice Struct for returning delegations
struct Delegation {
DelegationType type_;
address to;
address from;
bytes32 rights;
address contract_;
uint256 tokenId;
uint256 amount;
}
/// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
event DelegateERC721(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
bool enable
);
/**
* ----------- WRITE -----------
*/
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC721(
address to,
address contract_,
uint256 tokenId,
bytes32 rights,
bool enable
) external payable returns (bytes32 delegationHash);
/**
* @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
* @param to The delegated address to check
* @param contract_ The specific contract address being checked
* @param tokenId The token id for the token to delegating
* @param from The wallet that issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
*/
function checkDelegateForERC721(
address to,
address from,
address contract_,
uint256 tokenId,
bytes32 rights
) external view returns (bool);
/**
* ----------- ENUMERATIONS -----------
*/
/**
* @notice Returns all enabled delegations an address has given out
* @param from The address to retrieve delegations for
* @return delegations Array of Delegation structs
*/
function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to a Collateral Liquidation Receiver
*/
interface ICollateralLiquidationReceiver {
/**
* @notice Callback on collateral liquidated
* @dev Pre-conditions: 1) proceeds were transferred, and 2) transferred amount >= proceeds
* @param liquidationContext Liquidation context
* @param proceeds Liquidation proceeds in currency tokens
*/
function onCollateralLiquidated(bytes calldata liquidationContext, uint256 proceeds) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to a Collateral Liquidator
*/
interface ICollateralLiquidator {
/**
* @notice Get collateral liquidator name
* @return Collateral liquidator name
*/
function name() external view returns (string memory);
/**
* @notice Liquidate collateral
* @param currencyToken Currency token
* @param collateralToken Collateral token, either underlying token or collateral wrapper
* @param collateralTokenId Collateral token ID
* @param collateralWrapperContext Collateral wrapper context
* @param liquidationContext Liquidation callback context
*/
function liquidate(
address currencyToken,
address collateralToken,
uint256 collateralTokenId,
bytes calldata collateralWrapperContext,
bytes calldata liquidationContext
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to a Collateral Wrapper
*/
interface ICollateralWrapper {
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get collateral wrapper name
* @return Collateral wrapper name
*/
function name() external view returns (string memory);
/**
* @notice Enumerate wrapped collateral
* @param tokenId Collateral wrapper token ID
* @param context Implementation-specific context
* @return token Token address
* @return tokenIds List of unique token ids
*/
function enumerate(
uint256 tokenId,
bytes calldata context
) external view returns (address token, uint256[] memory tokenIds);
/**
* @notice Enumerate wrapped collateral with quantities of each token id
* @param tokenId Collateral wrapper token ID
* @param context Implementation-specific context
* @return token Token address
* @return tokenIds List of unique token ids
* @return quantities List of quantities of each token id
*/
function enumerateWithQuantities(
uint256 tokenId,
bytes calldata context
) external view returns (address token, uint256[] memory tokenIds, uint256[] memory quantities);
/**
* @notice Get total token count represented by wrapped collateral
* @param tokenId Collateral wrapper token ID
* @param context Implementation-specific context
* @return tokenCount Total token count
*/
function count(uint256 tokenId, bytes calldata context) external view returns (uint256 tokenCount);
/*
* Transfer collateral calldata
* @param token Collateral token
* @param from From address
* @param to To address
* @param tokenId Collateral wrapper token ID
* @param quantity Quantity of token ID
* @return target Transfer target
* @return data Transfer calldata
*/
function transferCalldata(
address token,
address from,
address to,
uint256 tokenId,
uint256 quantity
) external returns (address target, bytes memory data);
/*
* Unwrap collateral
* @param tokenId Collateral wrapper token ID
* @param context Implementation-specific context
*/
function unwrap(uint256 tokenId, bytes calldata context) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to Liquidity state
*/
interface ILiquidity {
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Insufficient liquidity
*/
error InsufficientLiquidity();
/**
* @notice Inactive liquidity
*/
error InactiveLiquidity();
/**
* @notice Insufficient tick spacing
*/
error InsufficientTickSpacing();
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @notice Flattened liquidity node returned by getter
* @param tick Tick
* @param value Liquidity value
* @param shares Liquidity shares outstanding
* @param available Liquidity available
* @param pending Liquidity pending (with interest)
* @param redemptions Total pending redemptions
* @param prev Previous liquidity node
* @param next Next liquidity node
*/
struct NodeInfo {
uint128 tick;
uint128 value;
uint128 shares;
uint128 available;
uint128 pending;
uint128 redemptions;
uint128 prev;
uint128 next;
}
/**
* @notice Accrual info returned by getter
* @param accrued Accrued interest
* @param rate Accrual rate
* @param timestamp Accrual timestamp
*/
struct AccrualInfo {
uint128 accrued;
uint64 rate;
uint64 timestamp;
}
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* Get liquidity nodes spanning [startTick, endTick] range
* @param startTick Start tick
* @param endTick End tick
* @return Liquidity nodes
*/
function liquidityNodes(uint128 startTick, uint128 endTick) external view returns (NodeInfo[] memory);
/**
* Get liquidity node at tick
* @param tick Tick
* @return Liquidity node
*/
function liquidityNode(uint128 tick) external view returns (NodeInfo memory);
/**
* Get liquidity node with accrual info at tick
* @param tick Tick
* @return Liquidity node, Accrual info
*/
function liquidityNodeWithAccrual(uint128 tick) external view returns (NodeInfo memory, AccrualInfo memory);
/**
* @notice Get deposit share price
* @param tick Tick
* @return Deposit share price
*/
function depositSharePrice(uint128 tick) external view returns (uint256);
/**
* @notice Get redemption share price
* @param tick Tick
* @return Redemption share price
*/
function redemptionSharePrice(uint128 tick) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to a Pool
*/
interface IPool {
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Invalid caller
*/
error InvalidCaller();
/**
* @notice Insufficient shares
*/
error InsufficientShares();
/**
* @notice Invalid redemption status
*/
error InvalidRedemptionStatus();
/**
* @notice Invalid loan receipt
*/
error InvalidLoanReceipt();
/**
* @notice Invalid borrow options
*/
error InvalidBorrowOptions();
/**
* @notice Unsupported collateral
* @param index Index of unsupported asset
*/
error UnsupportedCollateral(uint256 index);
/**
* @notice Unsupported loan duration
*/
error UnsupportedLoanDuration();
/**
* @notice Repayment too high
*/
error RepaymentTooHigh();
/**
* @notice Loan not expired
*/
error LoanNotExpired();
/**
* @notice Invalid parameters
*/
error InvalidParameters();
/**************************************************************************/
/* Events */
/**************************************************************************/
/**
* @notice Emitted when currency is deposited
* @param account Account
* @param tick Tick
* @param amount Amount of currency tokens
* @param shares Amount of shares allocated
*/
event Deposited(address indexed account, uint128 indexed tick, uint256 amount, uint256 shares);
/**
* @notice Emitted when deposit shares are redeemed
* @param account Account
* @param tick Tick
* @param redemptionId Redemption ID
* @param shares Amount of shares to be redeemed
*/
event Redeemed(address indexed account, uint128 indexed tick, uint128 indexed redemptionId, uint256 shares);
/**
* @notice Emitted when redeemed currency tokens are withdrawn
* @param account Account
* @param tick Tick
* @param redemptionId Redemption ID
* @param shares Amount of shares redeemed
* @param amount Amount of currency tokens withdrawn
*/
event Withdrawn(
address indexed account,
uint128 indexed tick,
uint128 indexed redemptionId,
uint256 shares,
uint256 amount
);
/**
* @notice Emitted when deposit shares are transferred
* @param from Source account
* @param to Destination account
* @param tick Tick
* @param shares Amount of shares transferred
*/
event Transferred(address indexed from, address indexed to, uint128 indexed tick, uint256 shares);
/**
* @notice Emitted when a loan is originated
* @param loanReceiptHash Loan receipt hash
* @param loanReceipt Loan receipt
*/
event LoanOriginated(bytes32 indexed loanReceiptHash, bytes loanReceipt);
/**
* @notice Emitted when a loan is repaid
* @param loanReceiptHash Loan receipt hash
* @param repayment Repayment amount in currency tokens
*/
event LoanRepaid(bytes32 indexed loanReceiptHash, uint256 repayment);
/**
* @notice Emitted when a loan is liquidated
* @param loanReceiptHash Loan receipt hash
*/
event LoanLiquidated(bytes32 indexed loanReceiptHash);
/**
* @notice Emitted when loan collateral is liquidated
* @param loanReceiptHash Loan receipt hash
* @param proceeds Total liquidation proceeds in currency tokens
* @param borrowerProceeds Borrower's share of liquidation proceeds in
* currency tokens
*/
event CollateralLiquidated(bytes32 indexed loanReceiptHash, uint256 proceeds, uint256 borrowerProceeds);
/**
* @notice Emitted when admin fee rate is updated
* @param rate New admin fee rate in basis points
*/
event AdminFeeRateUpdated(uint256 rate);
/**
* @notice Emitted when admin fees are withdrawn
* @param account Recipient account
* @param amount Amount of currency tokens withdrawn
*/
event AdminFeesWithdrawn(address indexed account, uint256 amount);
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* @notice Get currency token
* @return Currency token contract
*/
function currencyToken() external view returns (address);
/**
* @notice Get supported durations
* @return List of loan durations in second
*/
function durations() external view returns (uint64[] memory);
/**
* @notice Get supported rates
* @return List of rates in interest per second
*/
function rates() external view returns (uint64[] memory);
/**
* @notice Get admin
* @return Admin
*/
function admin() external view returns (address);
/**
* @notice Get admin fee rate
* @return Admin fee rate in basis points
*/
function adminFeeRate() external view returns (uint32);
/**
* @notice Get admin fee balance
* @return Admin fee balance in currency tokens
*/
function adminFeeBalance() external view returns (uint256);
/**
* @notice Get list of supported collateral wrappers
* @return Collateral wrappers
*/
function collateralWrappers() external view returns (address[] memory);
/**
* @notice Get collateral liquidator contract
* @return Collateral liquidator contract
*/
function collateralLiquidator() external view returns (address);
/**
* @notice Get delegation registry v1 contract
* @return Delegation registry contract
*/
function delegationRegistry() external view returns (address);
/**
* @notice Get delegation registry v2 contract
* @return Delegation registry contract
*/
function delegationRegistryV2() external view returns (address);
/**************************************************************************/
/* Deposit API */
/**************************************************************************/
/**
* @notice Deposit amount at tick
*
* Emits a {Deposited} event.
*
* @param tick Tick
* @param amount Amount of currency tokens
* @param minShares Minimum amount of shares to receive
* @return shares Amount of shares minted
*/
function deposit(uint128 tick, uint256 amount, uint256 minShares) external returns (uint256 shares);
/**
* @notice Redeem deposit shares for currency tokens. Currency tokens can
* be withdrawn with the `withdraw()` method once the redemption is
* processed.
*
* Emits a {Redeemed} event.
*
* @param tick Tick
* @param shares Amount of deposit shares to redeem
* @return redemptionId Redemption ID
*/
function redeem(uint128 tick, uint256 shares) external returns (uint128 redemptionId);
/**
* @notice Get redemption available
*
* @param account Account
* @param tick Tick
* @param redemptionId Redemption ID
* @return shares Amount of deposit shares available for redemption
* @return amount Amount of currency tokens available for withdrawal
* @return sharesAhead Amount of pending shares ahead in queue
*/
function redemptionAvailable(
address account,
uint128 tick,
uint128 redemptionId
) external view returns (uint256 shares, uint256 amount, uint256 sharesAhead);
/**
* @notice Withdraw a redemption that is available
*
* Emits a {Withdrawn} event.
*
* @param tick Tick
* @param redemptionId Redemption ID
* @return shares Amount of deposit shares burned
* @return amount Amount of currency tokens withdrawn
*/
function withdraw(uint128 tick, uint128 redemptionId) external returns (uint256 shares, uint256 amount);
/**
* @notice Rebalance a redemption that is available to a new tick
*
* Emits {Withdrawn} and {Deposited} events.
*
* @param srcTick Source tick
* @param dstTick Destination Tick
* @param redemptionId Redemption ID
* @param minShares Minimum amount of destination shares to receive
* @return oldShares Amount of source deposit shares burned
* @return newShares Amount of destination deposit shares minted
* @return amount Amount of currency tokens redeposited
*/
function rebalance(
uint128 srcTick,
uint128 dstTick,
uint128 redemptionId,
uint256 minShares
) external returns (uint256 oldShares, uint256 newShares, uint256 amount);
/**************************************************************************/
/* Lend API */
/**************************************************************************/
/**
* @notice Quote repayment for a loan
* @param principal Principal amount in currency tokens
* @param duration Duration in seconds
* @param collateralToken Collateral token address
* @param collateralTokenId Collateral token ID
* @param ticks Liquidity ticks
* @param options Encoded options
* @return repayment Repayment amount in currency tokens
*/
function quote(
uint256 principal,
uint64 duration,
address collateralToken,
uint256 collateralTokenId,
uint128[] calldata ticks,
bytes calldata options
) external view returns (uint256 repayment);
/**
* @notice Originate a loan
*
* Emits a {LoanOriginated} event.
*
* @param principal Principal amount in currency tokens
* @param duration Duration in seconds
* @param collateralToken Collateral token address
* @param collateralTokenId Collateral token ID
* @param maxRepayment Maximum repayment amount in currency tokens
* @param ticks Liquidity ticks
* @param options Encoded options
* @return repayment Repayment amount in currency tokens
*/
function borrow(
uint256 principal,
uint64 duration,
address collateralToken,
uint256 collateralTokenId,
uint256 maxRepayment,
uint128[] calldata ticks,
bytes calldata options
) external returns (uint256 repayment);
/**
* @notice Repay a loan
*
* Emits a {LoanRepaid} event.
*
* @param encodedLoanReceipt Encoded loan receipt
* @return repayment Repayment amount in currency tokens
*/
function repay(bytes calldata encodedLoanReceipt) external returns (uint256 repayment);
/**
* @notice Refinance a loan
*
* Emits a {LoanRepaid} event and a {LoanOriginated} event.
*
* @param encodedLoanReceipt Encoded loan receipt
* @param principal Principal amount in currency tokens
* @param duration Duration in seconds
* @param maxRepayment Maximum repayment amount in currency tokens
* @param ticks Liquidity ticks
* @param options Encoded options
* @return repayment Repayment amount in currency tokens
*/
function refinance(
bytes calldata encodedLoanReceipt,
uint256 principal,
uint64 duration,
uint256 maxRepayment,
uint128[] calldata ticks,
bytes calldata options
) external returns (uint256 repayment);
/**
* @notice Liquidate an expired loan
*
* Emits a {LoanLiquidated} event.
*
* @param loanReceipt Loan receipt
*/
function liquidate(bytes calldata loanReceipt) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Interface to a Price Oracle
*/
interface IPriceOracle {
/**
* @notice Fetch price of token IDs
* @param collateralToken Pool collateral token
* @param currencyToken Pool currency token
* @param tokenIds Token IDs
* @param tokenIdQuantities Token ID quantities
* @param oracleContext Oracle context
* @return price Token price as a fixed point 18 decimal
*/
function price(
address collateralToken,
address currencyToken,
uint256[] memory tokenIds,
uint256[] memory tokenIdQuantities,
bytes calldata oracleContext
) external view returns (uint256 price);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./interfaces/ILiquidity.sol";
import "./Tick.sol";
/**
* @title Liquidity Logic
* @author MetaStreet Labs
*/
library LiquidityLogic {
/*
* Liquidity nodes are arranged in a linked-list that starts with a zero
* sentinel and ends with an end sentinel. There are two types of ticks, namely,
* ratio ticks and absolute ticks (see more in Tick.sol). In the linked-list,
* ratio ticks are ordered before absolute ticks. Within the types, they are
* ordered in ascending order of their tick values.
*
* +--------------------------------------------------------------------+
* | Linked-List Layout |
* +----------------------------------|------------------|--------------+
* | 0 | (Limit Type = 1) | (Limit Type = 0) | Max. uint128 |
* | Zero Sentinel | Ratio Ticks | Absolute Ticks | End Sentinel |
* +--------------------------------------------------------------------+
*
*/
using SafeCast for uint256;
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Tick limit spacing basis points for absolute type (10%)
*/
uint256 internal constant ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS = 1000;
/**
* @notice Tick limit spacing basis points for ratio type (5%)
*/
uint256 internal constant RATIO_TICK_LIMIT_SPACING_BASIS_POINTS = 500;
/**
* @notice Fixed point scale
*/
uint256 internal constant FIXED_POINT_SCALE = 1e18;
/**
* @notice Basis points scale
*/
uint256 internal constant BASIS_POINTS_SCALE = 10_000;
/**
* @notice Impaired price threshold (5%)
*/
uint256 internal constant IMPAIRED_PRICE_THRESHOLD = 0.05 * 1e18;
/**
* @notice Max ticks used count
*/
uint256 private constant MAX_TICKS_USED_COUNT = 32;
/**
* @notice Max redemption queue scan count
*/
uint256 private constant MAX_REDEMPTION_QUEUE_SCAN_COUNT = 150;
/**
* @notice Amount of shares to lock for initial deposit
*/
uint128 private constant LOCKED_SHARES = 1e6;
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @notice Node source
* @param tick Tick
* @param used Amount used
* @param pending Amount pending
*/
struct NodeSource {
uint128 tick;
uint128 used;
uint128 pending;
}
/**
* @notice Fulfilled redemption
* @param shares Shares redeemed
* @param amount Amount redeemed
*/
struct FulfilledRedemption {
uint128 shares;
uint128 amount;
}
/**
* @notice Redemption state
* @param pending Pending shares
* @param index Current index
* @param fulfilled Fulfilled redemptions
*/
struct Redemptions {
uint128 pending;
uint128 index;
mapping(uint128 => FulfilledRedemption) fulfilled;
}
/**
* @notice Accrual state
* @param accrued Accrued interest
* @param rate Accrual rate
* @param timestamp Last accrual timestamp
*/
struct Accrual {
uint128 accrued;
uint64 rate;
uint64 timestamp;
}
/**
* @notice Liquidity node
* @param value Liquidity value
* @param shares Liquidity shares outstanding
* @param available Liquidity available
* @param pending Liquidity pending (with interest)
* @param prev Previous liquidity node
* @param next Next liquidity node
* @param redemption Redemption state
* @param accrual Accrual state
*/
struct Node {
uint128 value;
uint128 shares;
uint128 available;
uint128 pending;
uint128 prev;
uint128 next;
Redemptions redemptions;
Accrual accrual;
}
/**
* @notice Liquidity state
* @param nodes Liquidity nodes
*/
struct Liquidity {
mapping(uint256 => Node) nodes;
}
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* Get liquidity node at tick
* @param liquidity Liquidity state
* @param tick Tick
* @return Liquidity node
*/
function liquidityNode(Liquidity storage liquidity, uint128 tick) public view returns (ILiquidity.NodeInfo memory) {
Node storage node = liquidity.nodes[tick];
return
ILiquidity.NodeInfo({
tick: tick,
value: node.value,
shares: node.shares,
available: node.available,
pending: node.pending,
redemptions: node.redemptions.pending,
prev: node.prev,
next: node.next
});
}
/**
* @notice Count liquidity nodes spanning [startTick, endTick] range, where
* startTick is 0 or an instantiated tick
* @param liquidity Liquidity state
* @param startTick Start tick
* @param endTick End tick
* @return count Liquidity nodes count
*/
function liquidityNodesCount(
Liquidity storage liquidity,
uint128 startTick,
uint128 endTick
) public view returns (uint256 count) {
/* Validate start tick has active liquidity */
if (liquidity.nodes[startTick].next == 0) revert ILiquidity.InactiveLiquidity();
/* Count nodes */
uint256 t = startTick;
while (t != type(uint128).max && t <= endTick) {
t = liquidity.nodes[t].next;
count++;
}
}
/**
* @notice Get liquidity nodes spanning [startTick, endTick] range, where
* startTick is 0 or an instantiated tick
* @param liquidity Liquidity state
* @param startTick Start tick
* @param endTick End tick
* @return Liquidity nodes
*/
function liquidityNodes(
Liquidity storage liquidity,
uint128 startTick,
uint128 endTick
) external view returns (ILiquidity.NodeInfo[] memory) {
ILiquidity.NodeInfo[] memory nodes = new ILiquidity.NodeInfo[](
liquidityNodesCount(liquidity, startTick, endTick)
);
/* Populate nodes */
uint256 i;
uint128 t = startTick;
while (t != type(uint128).max && t <= endTick) {
nodes[i] = liquidityNode(liquidity, t);
t = nodes[i++].next;
}
return nodes;
}
/**
* @notice Get liquidity node with accrual info at tick
* @param liquidity Liquidity state
* @param tick Tick
* @return Liquidity node, Accrual info
*/
function liquidityNodeWithAccrual(
Liquidity storage liquidity,
uint128 tick
) external view returns (ILiquidity.NodeInfo memory, ILiquidity.AccrualInfo memory) {
Node storage node = liquidity.nodes[tick];
return (
ILiquidity.NodeInfo({
tick: tick,
value: node.value,
shares: node.shares,
available: node.available,
pending: node.pending,
redemptions: node.redemptions.pending,
prev: node.prev,
next: node.next
}),
ILiquidity.AccrualInfo({
accrued: node.accrual.accrued,
rate: node.accrual.rate,
timestamp: node.accrual.timestamp
})
);
}
/**
* @notice Get redemption available amount
* @param liquidity Liquidity state
* @param tick Tick
* @param pending Redemption pending
* @param index Redemption index
* @param target Redemption target
* @return redeemedShares Redeemed shares
* @return redeemedAmount Redeemed amount
* @return processedIndices Processed indices
* @return processedShares Processed shares
*/
function redemptionAvailable(
Liquidity storage liquidity,
uint128 tick,
uint128 pending,
uint128 index,
uint128 target
)
internal
view
returns (uint128 redeemedShares, uint128 redeemedAmount, uint128 processedIndices, uint128 processedShares)
{
Node storage node = liquidity.nodes[tick];
uint256 stopIndex = index + MAX_REDEMPTION_QUEUE_SCAN_COUNT;
for (; processedShares < target + pending && index < stopIndex; index++) {
if (index == node.redemptions.index) {
/* Reached pending unfulfilled redemption */
break;
}
/* Look up the next fulfilled redemption */
FulfilledRedemption storage redemption = node.redemptions.fulfilled[index];
/* Update processed count */
processedIndices += 1;
processedShares += redemption.shares;
if (processedShares <= target) {
/* Have not reached the redemption queue position yet */
continue;
} else {
/* Compute number of shares to redeem in range of this
* redemption batch */
uint128 shares = (((processedShares > target + pending) ? pending : (processedShares - target))) -
redeemedShares;
/* Compute price of shares in this redemption batch */
uint256 price = (redemption.amount * FIXED_POINT_SCALE) / redemption.shares;
/* Accumulate redeemed shares and corresponding amount */
redeemedShares += shares;
redeemedAmount += Math.mulDiv(shares, price, FIXED_POINT_SCALE).toUint128();
}
}
}
/**
* @notice Get deposit share price
* @param liquidity Liquidity state
* @param tick Tick
* @return Deposit share price
*/
function depositSharePrice(Liquidity storage liquidity, uint128 tick) external view returns (uint256) {
Node storage node = liquidity.nodes[tick];
/* Simulate accrual */
uint128 accrued = node.accrual.accrued + node.accrual.rate * uint128(block.timestamp - node.accrual.timestamp);
/* Return deposit price */
return
node.shares == 0
? FIXED_POINT_SCALE
: (Math.min(node.value + accrued, node.available + node.pending) * FIXED_POINT_SCALE) / node.shares;
}
/**
* @notice Get redemption share price
* @param liquidity Liquidity state
* @param tick Tick
* @return Redemption share price
*/
function redemptionSharePrice(Liquidity storage liquidity, uint128 tick) external view returns (uint256) {
Node storage node = liquidity.nodes[tick];
/* Revert if node is empty */
if (node.value == 0 || node.shares == 0) revert ILiquidity.InactiveLiquidity();
/* Return redemption price */
return (node.value * FIXED_POINT_SCALE) / node.shares;
}
/**************************************************************************/
/* Internal Helpers */
/**************************************************************************/
/**
* @dev Check if tick is reserved
* @param tick Tick
* @return True if reserved, otherwise false
*/
function _isReserved(uint128 tick) internal pure returns (bool) {
return tick == 0 || tick == type(uint128).max;
}
/**
* @dev Check if liquidity node is empty
* @param node Liquidity node
* @return True if empty, otherwise false
*/
function _isEmpty(Node storage node) internal view returns (bool) {
return node.shares <= LOCKED_SHARES && node.pending == 0;
}
/**
* @dev Check if liquidity node is active
* @param node Liquidity node
* @return True if active, otherwise false
*/
function _isActive(Node storage node) internal view returns (bool) {
return node.prev != 0 || node.next != 0;
}
/**
* @dev Check if liquidity node is impaired
* @param node Liquidity node
* @return True if impaired, otherwise false
*/
function _isImpaired(Node storage node) internal view returns (bool) {
/* If there's shares, but insufficient value for a stable share price */
return node.shares != 0 && node.value * FIXED_POINT_SCALE < node.shares * IMPAIRED_PRICE_THRESHOLD;
}
/**
* @notice Instantiate liquidity
* @param liquidity Liquidity state
* @param node Liquidity node
* @param tick Tick
*/
function _instantiate(Liquidity storage liquidity, Node storage node, uint128 tick) internal {
/* If node is active, do nothing */
if (_isActive(node)) return;
/* If node is inactive and not empty, revert */
if (!_isEmpty(node)) revert ILiquidity.InactiveLiquidity();
/* Instantiate previous tick and previous node */
uint128 prevTick;
Node storage prevNode = liquidity.nodes[prevTick];
/* Decode limit and limit type from new tick and next tick */
(uint256 newLimit, , , Tick.LimitType newLimitType) = Tick.decode(tick, BASIS_POINTS_SCALE);
(uint256 nextLimit, , , Tick.LimitType nextLimitType) = Tick.decode(prevNode.next, BASIS_POINTS_SCALE);
/* Find prior node to new tick */
bool isAbsoluteType = newLimitType == Tick.LimitType.Absolute;
while (nextLimitType == newLimitType ? nextLimit < newLimit : isAbsoluteType) {
prevTick = prevNode.next;
prevNode = liquidity.nodes[prevTick];
/* Decode limit and limit type from next tick */
(nextLimit, , , nextLimitType) = Tick.decode(prevNode.next, BASIS_POINTS_SCALE);
}
/* Decode limit and limit type from prev tick */
(uint256 prevLimit, , , Tick.LimitType prevLimitType) = Tick.decode(prevTick, BASIS_POINTS_SCALE);
/* Validate tick limit spacing */
if (isAbsoluteType) {
/* Validate new absolute limit */
if (
newLimit != prevLimit &&
prevLimitType == Tick.LimitType.Absolute &&
newLimit <
(prevLimit * (BASIS_POINTS_SCALE + ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS)) / BASIS_POINTS_SCALE
) revert ILiquidity.InsufficientTickSpacing();
if (
newLimit != nextLimit &&
nextLimitType == Tick.LimitType.Absolute &&
nextLimit <
(newLimit * (BASIS_POINTS_SCALE + ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS)) / BASIS_POINTS_SCALE
) revert ILiquidity.InsufficientTickSpacing();
} else {
/* Validate new ratio limit */
if (
newLimit != prevLimit &&
prevLimitType == Tick.LimitType.Ratio &&
newLimit < (prevLimit + RATIO_TICK_LIMIT_SPACING_BASIS_POINTS)
) revert ILiquidity.InsufficientTickSpacing();
if (
newLimit != nextLimit &&
nextLimitType == Tick.LimitType.Ratio &&
nextLimit < (newLimit + RATIO_TICK_LIMIT_SPACING_BASIS_POINTS)
) revert ILiquidity.InsufficientTickSpacing();
}
/* Link new node */
node.prev = prevTick;
node.next = prevNode.next;
liquidity.nodes[prevNode.next].prev = tick;
prevNode.next = tick;
}
/**
* @dev Garbage collect an impaired or empty node, unlinking it from active
* liquidity
* @param liquidity Liquidity state
* @param node Liquidity node
*/
function _garbageCollect(Liquidity storage liquidity, Node storage node) internal {
/* If node is not impaired and not empty, or already inactive, do nothing */
if ((!_isImpaired(node) && !_isEmpty(node)) || !_isActive(node)) return;
/* Make node inactive by unlinking it */
liquidity.nodes[node.prev].next = node.next;
liquidity.nodes[node.next].prev = node.prev;
node.next = 0;
node.prev = 0;
}
/**
* @notice Process redemptions from available liquidity
* @param liquidity Liquidity state
* @param node Liquidity node
*/
function _processRedemptions(Liquidity storage liquidity, Node storage node) internal {
/* If there's no pending shares to redeem */
if (node.redemptions.pending == 0) return;
/* Compute redemption price */
uint256 price = (node.value * FIXED_POINT_SCALE) / node.shares;
if (price == 0) {
/* If node has pending interest */
if (node.pending != 0) return;
/* If node is insolvent, redeem all shares for zero amount */
uint128 shares = node.redemptions.pending;
/* Record fulfilled redemption */
node.redemptions.fulfilled[node.redemptions.index++] = FulfilledRedemption({shares: shares, amount: 0});
/* Update node state */
node.shares -= shares;
node.value = 0;
node.available = 0;
node.redemptions.pending = 0;
return;
} else {
/* Node is solvent */
/* If there's no cash to redeem from */
if (node.available == 0) return;
/* Redeem as many shares as possible and pending from available cash */
uint128 shares = uint128(Math.min((node.available * FIXED_POINT_SCALE) / price, node.redemptions.pending));
uint128 amount = Math.mulDiv(shares, price, FIXED_POINT_SCALE).toUint128();
/* If there's insufficient cash to redeem non-zero pending shares
* at current price */
if (shares == 0) return;
/* Record fulfilled redemption */
node.redemptions.fulfilled[node.redemptions.index++] = FulfilledRedemption({
shares: shares,
amount: amount
});
/* Update node state */
node.shares -= shares;
node.value -= amount;
node.available -= amount;
node.redemptions.pending -= shares;
/* Garbage collect node if it is now empty */
_garbageCollect(liquidity, node);
return;
}
}
/**
* @notice Process accrued value from accrual rate and timestamp
* @param node Liquidity node
*/
function _accrue(Node storage node) internal {
node.accrual.accrued += node.accrual.rate * uint128(block.timestamp - node.accrual.timestamp);
node.accrual.timestamp = uint64(block.timestamp);
}
/**************************************************************************/
/* Primary API */
/**************************************************************************/
/**
* @notice Initialize liquidity state
* @param liquidity Liquidity state
*/
function initialize(Liquidity storage liquidity) internal {
/* Liquidity state defaults to zero, but need to make head and tail nodes */
liquidity.nodes[0].next = type(uint128).max;
/* liquidity.nodes[type(uint128).max].prev = 0 by default */
}
/**
* @notice Deposit liquidity
* @param liquidity Liquidity state
* @param tick Tick
* @param amount Amount
* @return Number of shares
*/
function deposit(Liquidity storage liquidity, uint128 tick, uint128 amount) internal returns (uint128) {
Node storage node = liquidity.nodes[tick];
/* If tick is reserved */
if (_isReserved(tick)) revert ILiquidity.InactiveLiquidity();
/* Instantiate node, if necessary */
_instantiate(liquidity, node, tick);
/* Process accrual */
_accrue(node);
/* Compute deposit price */
uint256 price = node.shares == 0
? FIXED_POINT_SCALE
: (Math.min(node.value + node.accrual.accrued, node.available + node.pending) * FIXED_POINT_SCALE) /
node.shares;
/* Compute shares and depositor's shares */
uint128 shares = ((amount * FIXED_POINT_SCALE) / price).toUint128();
/* If this is the initial deposit, lock subset of shares */
bool initialDeposit = node.shares < LOCKED_SHARES;
node.value += amount;
node.shares += shares;
node.available += amount;
/* Process any pending redemptions from available cash */
_processRedemptions(liquidity, node);
return initialDeposit ? shares - LOCKED_SHARES : shares;
}
/**
* @notice Use liquidity from node
* @param liquidity Liquidity state
* @param tick Tick
* @param used Used amount
* @param pending Pending amount
* @param duration Duration
*/
function use(Liquidity storage liquidity, uint128 tick, uint128 used, uint128 pending, uint64 duration) internal {
Node storage node = liquidity.nodes[tick];
node.available -= used;
node.pending += pending;
/* Process accrual */
_accrue(node);
/* Increment accrual rate */
uint256 rate = uint256(pending - used) / duration;
node.accrual.rate += rate.toUint64();
}
/**
* @notice Restore liquidity and process pending redemptions
* @param liquidity Liquidity state
* @param tick Tick
* @param used Used amount
* @param pending Pending amount
* @param restored Restored amount
* @param duration Duration
* @param elapsed Elapsed time since loan origination
*/
function restore(
Liquidity storage liquidity,
uint128 tick,
uint128 used,
uint128 pending,
uint128 restored,
uint64 duration,
uint64 elapsed
) internal {
Node storage node = liquidity.nodes[tick];
node.value = node.value - used + restored;
node.available += restored;
node.pending -= pending;
/* Garbage collect node if it is now impaired */
_garbageCollect(liquidity, node);
/* Process any pending redemptions */
_processRedemptions(liquidity, node);
/* Process accrual */
_accrue(node);
/* Decrement accrual rate and accrued */
uint256 rate = uint256(pending - used) / duration;
node.accrual.rate -= rate.toUint64();
node.accrual.accrued -= uint128(rate * elapsed);
}
/**
* @notice Redeem liquidity
* @param liquidity Liquidity state
* @param tick Tick
* @param shares Shares
* @return Redemption index, Redemption target
*/
function redeem(Liquidity storage liquidity, uint128 tick, uint128 shares) internal returns (uint128, uint128) {
Node storage node = liquidity.nodes[tick];
/* Redemption from inactive liquidity nodes is allowed to facilitate
* restoring garbage collected nodes */
/* Snapshot redemption target */
uint128 redemptionIndex = node.redemptions.index;
uint128 redemptionTarget = node.redemptions.pending;
/* Add shares to pending redemptions */
node.redemptions.pending += shares;
/* Initialize redemption record to save gas in loan callbacks */
if (node.redemptions.fulfilled[redemptionIndex].shares != type(uint128).max) {
node.redemptions.fulfilled[redemptionIndex] = FulfilledRedemption({shares: type(uint128).max, amount: 0});
}
/* Process any pending redemptions from available cash */
_processRedemptions(liquidity, node);
return (redemptionIndex, redemptionTarget);
}
/**
* @notice Source liquidity from nodes
* @param liquidity Liquidity state
* @param amount Amount
* @param ticks Ticks to source from
* @param multiplier Multiplier for amount
* @param durationIndex Duration index for amount
* @param oraclePrice Collateral token price from price oracle, if any
* @return Sourced liquidity nodes, count of nodes
*/
function source(
Liquidity storage liquidity,
uint256 amount,
uint128[] calldata ticks,
uint256 multiplier,
uint256 durationIndex,
uint256 oraclePrice
) internal view returns (NodeSource[] memory, uint16) {
NodeSource[] memory sources = new NodeSource[](ticks.length);
uint128 prevTick;
uint256 taken;
uint256 count;
for (; count < ticks.length && taken != amount; count++) {
uint128 tick = ticks[count];
/* Validate tick and decode limit */
uint256 limit = Tick.validate(tick, prevTick, durationIndex, oraclePrice);
/* Look up liquidity node */
Node storage node = liquidity.nodes[tick];
/* Consume as much as possible up to the tick limit, amount available, and amount remaining */
uint128 take = uint128(Math.min(Math.min(limit * multiplier - taken, node.available), amount - taken));
/* Record the liquidity allocation in our sources list */
sources[count] = NodeSource({tick: tick, used: take, pending: 0});
taken += take;
prevTick = tick;
}
/* If unable to source required liquidity amount from provided ticks */
if (taken < amount) revert ILiquidity.InsufficientLiquidity();
/* If count exceeds max number of ticks */
if (count > MAX_TICKS_USED_COUNT) revert ILiquidity.InsufficientLiquidity();
return (sources, count.toUint16());
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
/**
* @title LoanReceipt
* @author MetaStreet Labs
*/
library LoanReceipt {
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Invalid receipt encoding
*/
error InvalidReceiptEncoding();
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Loan receipt version
*/
uint8 internal constant LOAN_RECEIPT_VERSION = 2;
/**
* @notice Loan receipt header size in bytes
* @dev Header excludes borrow options byte array
*/
uint256 internal constant LOAN_RECEIPT_HEADER_SIZE = 187;
/**
* @notice Loan receipt node receipt size in bytes
*/
uint256 internal constant LOAN_RECEIPT_NODE_RECEIPT_SIZE = 48;
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @notice LoanReceiptV2
* @param version Version (2)
* @param principal Principal amount in currency tokens
* @param repayment Repayment amount in currency tokens
* @param adminFee Admin fee amount in currency tokens
* @param borrower Borrower
* @param maturity Loan maturity timestamp
* @param duration Loan duration
* @param collateralToken Collateral token
* @param collateralTokenId Collateral token ID
* @param collateralWrapperContextLen Collateral wrapper context length
* @param collateralWrapperContext Collateral wrapper context data
* @param nodeReceipts Node receipts
*/
struct LoanReceiptV2 {
uint8 version;
uint256 principal;
uint256 repayment;
uint256 adminFee;
address borrower;
uint64 maturity;
uint64 duration;
address collateralToken;
uint256 collateralTokenId;
uint16 collateralWrapperContextLen;
bytes collateralWrapperContext;
NodeReceipt[] nodeReceipts;
}
/**
* @notice Node receipt
* @param tick Tick
* @param used Used amount
* @param pending Pending amount
*/
struct NodeReceipt {
uint128 tick;
uint128 used;
uint128 pending;
}
/**************************************************************************/
/* Tightly packed format */
/**************************************************************************/
/*
Header (187 bytes)
1 uint8 version 0:1
32 uint256 principal 1:33
32 uint256 repayment 33:65
32 uint256 adminFee 65:97
20 address borrower 97:117
8 uint64 maturity 117:125
8 uint64 duration 125:133
20 address collateralToken 133:153
32 uint256 collateralTokenId 153:185
2 uint16 collateralWrapperContextLen 185:187
Collateral Wrapper Context Data (M bytes) 187:---
Node Receipts (48 * N bytes)
N NodeReceipts[] nodeReceipts
16 uint128 tick
16 uint128 used
16 uint128 pending
*/
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @dev Compute loan receipt hash
* @param encodedReceipt Encoded loan receipt
* @return Loan Receipt hash
*/
function hash(bytes memory encodedReceipt) internal view returns (bytes32) {
/* Take hash of chain ID (32 bytes) concatenated with encoded loan receipt */
return keccak256(abi.encodePacked(block.chainid, encodedReceipt));
}
/**
* @dev Encode a loan receipt into bytes
* @param receipt Loan Receipt
* @return Encoded loan receipt
*/
function encode(LoanReceiptV2 memory receipt) internal pure returns (bytes memory) {
/* Encode header */
bytes memory header = abi.encodePacked(
receipt.version,
receipt.principal,
receipt.repayment,
receipt.adminFee,
receipt.borrower,
receipt.maturity,
receipt.duration,
receipt.collateralToken,
receipt.collateralTokenId,
receipt.collateralWrapperContextLen,
receipt.collateralWrapperContext
);
/* Encode node receipts */
bytes memory nodeReceipts;
for (uint256 i; i < receipt.nodeReceipts.length; i++) {
nodeReceipts = abi.encodePacked(
nodeReceipts,
receipt.nodeReceipts[i].tick,
receipt.nodeReceipts[i].used,
receipt.nodeReceipts[i].pending
);
}
return abi.encodePacked(header, nodeReceipts);
}
/**
* @dev Decode a loan receipt from bytes
* @param encodedReceipt Encoded loan Receipt
* @return Decoded loan receipt
*/
function decode(bytes calldata encodedReceipt) internal pure returns (LoanReceiptV2 memory) {
/* Validate encoded receipt length */
if (encodedReceipt.length < LOAN_RECEIPT_HEADER_SIZE) revert InvalidReceiptEncoding();
uint256 collateralWrapperContextLen = uint16(bytes2(encodedReceipt[185:187]));
/* Validate length with collateral wrapper context */
if (encodedReceipt.length < LOAN_RECEIPT_HEADER_SIZE + collateralWrapperContextLen)
revert InvalidReceiptEncoding();
/* Validate length with node receipts */
if (
(encodedReceipt.length - LOAN_RECEIPT_HEADER_SIZE - collateralWrapperContextLen) %
LOAN_RECEIPT_NODE_RECEIPT_SIZE !=
0
) revert InvalidReceiptEncoding();
/* Validate encoded receipt version */
if (uint8(encodedReceipt[0]) != LOAN_RECEIPT_VERSION) revert InvalidReceiptEncoding();
LoanReceiptV2 memory receipt;
/* Decode header */
receipt.version = uint8(encodedReceipt[0]);
receipt.principal = uint256(bytes32(encodedReceipt[1:33]));
receipt.repayment = uint256(bytes32(encodedReceipt[33:65]));
receipt.adminFee = uint256(bytes32(encodedReceipt[65:97]));
receipt.borrower = address(uint160(bytes20(encodedReceipt[97:117])));
receipt.maturity = uint64(bytes8(encodedReceipt[117:125]));
receipt.duration = uint64(bytes8(encodedReceipt[125:133]));
receipt.collateralToken = address(uint160(bytes20(encodedReceipt[133:153])));
receipt.collateralTokenId = uint256(bytes32(encodedReceipt[153:185]));
receipt.collateralWrapperContextLen = uint16(collateralWrapperContextLen);
receipt.collateralWrapperContext = encodedReceipt[187:187 + collateralWrapperContextLen];
/* Decode node receipts */
uint256 numNodeReceipts = (encodedReceipt.length - LOAN_RECEIPT_HEADER_SIZE - collateralWrapperContextLen) /
LOAN_RECEIPT_NODE_RECEIPT_SIZE;
receipt.nodeReceipts = new NodeReceipt[](numNodeReceipts);
uint256 offset = LOAN_RECEIPT_HEADER_SIZE + collateralWrapperContextLen;
for (uint256 i; i < numNodeReceipts; i++) {
receipt.nodeReceipts[i].tick = uint128(bytes16(encodedReceipt[offset:offset + 16]));
receipt.nodeReceipts[i].used = uint128(bytes16(encodedReceipt[offset + 16:offset + 32]));
receipt.nodeReceipts[i].pending = uint128(bytes16(encodedReceipt[offset + 32:offset + 48]));
offset += LOAN_RECEIPT_NODE_RECEIPT_SIZE;
}
return receipt;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "./PriceOracle.sol";
import "../interfaces/IPriceOracle.sol";
/**
* @title External Price Oracle
* @author MetaStreet Labs
*/
contract ExternalPriceOracle is PriceOracle {
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @custom:storage-location erc7201:externalPriceOracle.priceOracleStorage
* @param addr Price oracle address
*/
struct PriceOracleStorage {
address addr;
}
/**************************************************************************/
/* State */
/**************************************************************************/
/**
* @notice Price oracle storage slot
* @dev keccak256(abi.encode(uint256(keccak256("externalPriceOracle.priceOracleStorage")) - 1)) & ~bytes32(uint256(0xff));
*/
bytes32 private constant PRICE_ORACLE_LOCATION = 0x5cc3a0ef4fb602d81e01a142e768b704108e3b2e96852939d75763e011a39b00;
/**************************************************************************/
/* Initializer */
/**************************************************************************/
/**
* @notice ExternalPriceOracle initializer
*/
function __initialize(address addr) internal {
_getPriceOracleStorage().addr = addr;
}
/**************************************************************************/
/* Internal Helpers */
/**************************************************************************/
/**
* @notice Get reference to ERC-7201 price oracle address storage
*
* @return $ Reference to price oracle address storage
*/
function _getPriceOracleStorage() private pure returns (PriceOracleStorage storage $) {
assembly {
$.slot := PRICE_ORACLE_LOCATION
}
}
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get price oracle address
*
* @return Price oracle address
*/
function priceOracle() public view returns (address) {
return _getPriceOracleStorage().addr;
}
/**
* @inheritdoc PriceOracle
*/
function price(
address collateralToken,
address currencyToken,
uint256[] memory tokenIds,
uint256[] memory tokenIdQuantities,
bytes calldata oracleContext
) public view override returns (uint256) {
/* Cache price oracle address */
address priceOracle_ = priceOracle();
/* Return oracle price if price oracle exists, else 0 */
return
priceOracle_ != address(0)
? IPriceOracle(priceOracle_).price(
collateralToken,
currencyToken,
tokenIds,
tokenIdQuantities,
oracleContext
)
: 0;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
/**
* @title Price oracle API
* @author MetaStreet Labs
*/
abstract contract PriceOracle {
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Fetch price of token IDs
* @param collateralToken Collateral token
* @param currencyToken Currency token
* @param tokenIds Token IDs
* @param tokenIdQuantities Token ID quantities
* @param oracleContext Oracle context
* @return Price as a fixed point 18 decimal
*/
function price(
address collateralToken,
address currencyToken,
uint256[] memory tokenIds,
uint256[] memory tokenIdQuantities,
bytes calldata oracleContext
) public view virtual returns (uint256);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./filters/CollateralFilter.sol";
import "./rates/InterestRateModel.sol";
import "./tokenization/DepositToken.sol";
import "./LoanReceipt.sol";
import "./LiquidityLogic.sol";
import "./DepositLogic.sol";
import "./BorrowLogic.sol";
import "./oracle/PriceOracle.sol";
import "./interfaces/IPool.sol";
import "./interfaces/ILiquidity.sol";
import "./interfaces/ICollateralWrapper.sol";
import "./interfaces/ICollateralLiquidator.sol";
import "./interfaces/ICollateralLiquidationReceiver.sol";
/**
* @title Pool
* @author MetaStreet Labs
*/
abstract contract Pool is
ERC165,
ReentrancyGuard,
Multicall,
CollateralFilter,
InterestRateModel,
DepositToken,
PriceOracle,
IPool,
ILiquidity,
ICollateralLiquidationReceiver
{
using SafeCast for uint256;
using SafeERC20 for IERC20;
using LiquidityLogic for LiquidityLogic.Liquidity;
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Tick spacing basis points for absolute type
*/
uint256 public constant ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS =
LiquidityLogic.ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS;
/**
* @notice Tick spacing basis points for ratio type
*/
uint256 public constant RATIO_TICK_LIMIT_SPACING_BASIS_POINTS =
LiquidityLogic.RATIO_TICK_LIMIT_SPACING_BASIS_POINTS;
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @notice Redemption
* @param pending Redemption shares pending
* @param index Redemption queue index
* @param target Redemption queue target
*/
struct Redemption {
uint128 pending;
uint128 index;
uint128 target;
}
/**
* @notice Deposit
* @param shares Shares
* @param redemptionId Next Redemption ID
* @param redemptions Mapping of redemption ID to redemption
*/
struct Deposit {
uint128 shares;
uint128 redemptionId;
mapping(uint128 => Redemption) redemptions;
}
/**
* @notice Delegate
* @param version Delegate version
* @param to Delegate address
*/
struct Delegate {
DelegateVersion version;
address to;
}
/**
* @custom:storage-location erc7201:pool.delegateStorage
*/
struct DelegateStorage {
/* Mapping of collateralToken to token ID to Delegate */
mapping(address => mapping(uint256 => Delegate)) delegates;
}
/**
* @notice Loan status
*/
enum LoanStatus {
Uninitialized,
Active,
Repaid,
Liquidated,
CollateralLiquidated
}
/**
* @notice Borrow function options
*/
enum BorrowOptions {
None,
CollateralWrapperContext,
CollateralFilterContext,
DelegateCashV1,
DelegateCashV2,
OracleContext
}
/**
* @notice Delegate version
*/
enum DelegateVersion {
None,
DelegateCashV1,
DelegateCashV2
}
/**************************************************************************/
/* Immutable State */
/**************************************************************************/
/**
* @notice Collateral wrappers (max 3)
*/
address internal immutable _collateralWrapper1;
address internal immutable _collateralWrapper2;
address internal immutable _collateralWrapper3;
/**
* @notice Collateral liquidator
*/
ICollateralLiquidator internal immutable _collateralLiquidator;
/**
* @notice Delegate registry v1 contract
*/
address internal immutable _delegateRegistryV1;
/**
* @notice Delegate registry v2 contract
*/
address internal immutable _delegateRegistryV2;
/**
* @notice Delegate cash storage slot
* @dev keccak256(abi.encode(uint256(keccak256("erc7201:pool.delegateStorage")) - 1)) & ~bytes32(uint256(0xff));
* @dev Erroneous inclusion of "erc7201" in the above namespace ID. No intention to fix.
*/
bytes32 internal constant DELEGATE_STORAGE_LOCATION =
0xf0e5094ebd597f2042580340ce53d1b15e5b64e0d8be717ecde51dd37c619300;
/**************************************************************************/
/* State */
/**************************************************************************/
/**
* @notice Pool Storage
* @param currencyToken Currency token contract
* @param adminFeeRate Admin free rate in basis points
* @param durations Durations
* @param rates Rates
* @param admin Admin
* @param adminFeeBalance Admin fee balance
* @param liquidity Liquidity
* @param deposits Mapping of account to tick to deposit
* @param loans Mapping of loan receipt hash to loan status
*/
struct PoolStorage {
IERC20 currencyToken;
uint32 adminFeeRate;
uint64[] durations;
uint64[] rates;
address admin;
uint256 adminFeeBalance;
LiquidityLogic.Liquidity liquidity;
mapping(address => mapping(uint128 => Deposit)) deposits;
mapping(bytes32 => LoanStatus) loans;
}
/**
* @notice Pool state
*/
PoolStorage internal _storage;
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice Pool constructor
* @param collateralLiquidator_ Collateral liquidator
* @param delegateRegistryV1_ Delegate registry v1 contract
* @param delegateRegistryV2_ Delegate registry v2 contract
* @param collateralWrappers_ Collateral wrappers
*/
constructor(
address collateralLiquidator_,
address delegateRegistryV1_,
address delegateRegistryV2_,
address[] memory collateralWrappers_
) {
if (collateralWrappers_.length > 3) revert InvalidParameters();
_collateralLiquidator = ICollateralLiquidator(collateralLiquidator_);
_delegateRegistryV1 = delegateRegistryV1_;
_delegateRegistryV2 = delegateRegistryV2_;
_collateralWrapper1 = (collateralWrappers_.length > 0) ? collateralWrappers_[0] : address(0);
_collateralWrapper2 = (collateralWrappers_.length > 1) ? collateralWrappers_[1] : address(0);
_collateralWrapper3 = (collateralWrappers_.length > 2) ? collateralWrappers_[2] : address(0);
}
/**************************************************************************/
/* Initializer */
/**************************************************************************/
/**
* @notice Pool initializer
* @dev Fee-on-transfer currency tokens are not supported
* @param currencyToken_ Currency token contract
* @param durations_ Duration tiers
* @param rates_ Interest rate tiers
*/
function _initialize(address currencyToken_, uint64[] memory durations_, uint64[] memory rates_) internal {
if (IERC20Metadata(currencyToken_).decimals() > 18) revert InvalidParameters();
_storage.currencyToken = IERC20(currencyToken_);
_storage.admin = msg.sender;
/* Assign durations */
if (durations_.length > Tick.MAX_NUM_DURATIONS) revert InvalidParameters();
for (uint256 i; i < durations_.length; i++) {
/* Check duration is monotonic */
if (i != 0 && durations_[i] >= durations_[i - 1]) revert InvalidParameters();
_storage.durations.push(durations_[i]);
}
/* Assign rates */
if (rates_.length > Tick.MAX_NUM_RATES) revert InvalidParameters();
for (uint256 i; i < rates_.length; i++) {
/* Check rate is monotonic */
if (i != 0 && rates_[i] <= rates_[i - 1]) revert InvalidParameters();
_storage.rates.push(rates_[i]);
}
/* Initialize liquidity */
_storage.liquidity.initialize();
}
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* @notice Get implementation name
* @return Implementation name
*/
function IMPLEMENTATION_NAME() external pure virtual returns (string memory);
/**
* @notice Get implementation version
* @return Implementation version
*/
function IMPLEMENTATION_VERSION() external pure returns (string memory) {
return "2.10";
}
/**
* @inheritdoc IPool
*/
function currencyToken() external view returns (address) {
return address(_storage.currencyToken);
}
/**
* @inheritdoc IPool
*/
function durations() external view returns (uint64[] memory) {
return _storage.durations;
}
/**
* @inheritdoc IPool
*/
function rates() external view returns (uint64[] memory) {
return _storage.rates;
}
/**
* @inheritdoc IPool
*/
function admin() external view returns (address) {
return _storage.admin;
}
/**
* @inheritdoc IPool
*/
function adminFeeRate() external view returns (uint32) {
return _storage.adminFeeRate;
}
/**
* @inheritdoc IPool
*/
function adminFeeBalance() external view returns (uint256) {
return _unscale(_storage.adminFeeBalance, false);
}
/**
* @inheritdoc IPool
*/
function collateralWrappers() external view returns (address[] memory) {
address[] memory collateralWrappers_ = new address[](3);
collateralWrappers_[0] = _collateralWrapper1;
collateralWrappers_[1] = _collateralWrapper2;
collateralWrappers_[2] = _collateralWrapper3;
return collateralWrappers_;
}
/**
* @inheritdoc IPool
*/
function collateralLiquidator() external view returns (address) {
return address(_collateralLiquidator);
}
/**
* @inheritdoc IPool
*/
function delegationRegistry() external view returns (address) {
return address(_delegateRegistryV1);
}
/**
* @inheritdoc IPool
*/
function delegationRegistryV2() external view returns (address) {
return address(_delegateRegistryV2);
}
/**
* @notice Get deposit
* @param account Account
* @param tick Tick
* @return shares Shares
* @return redemptionId Redemption ID
*/
function deposits(address account, uint128 tick) external view returns (uint128 shares, uint128 redemptionId) {
shares = _storage.deposits[account][tick].shares;
redemptionId = _storage.deposits[account][tick].redemptionId;
}
/**
* @notice Get redemption
* @param account Account
* @param tick Tick
* @param redemptionId Redemption ID
* @return Redemption
*/
function redemptions(
address account,
uint128 tick,
uint128 redemptionId
) external view returns (Redemption memory) {
return _storage.deposits[account][tick].redemptions[redemptionId];
}
/**
* @notice Get loan status
* @param receiptHash Loan receipt hash
* @return Loan status
*/
function loans(bytes32 receiptHash) external view returns (LoanStatus) {
return _storage.loans[receiptHash];
}
/**
* @inheritdoc ILiquidity
*/
function liquidityNodes(uint128 startTick, uint128 endTick) external view returns (NodeInfo[] memory) {
return _storage.liquidity.liquidityNodes(startTick, endTick);
}
/**
* @inheritdoc ILiquidity
*/
function liquidityNode(uint128 tick) external view returns (NodeInfo memory) {
return _storage.liquidity.liquidityNode(tick);
}
/**
* @inheritdoc ILiquidity
*/
function liquidityNodeWithAccrual(uint128 tick) external view returns (NodeInfo memory, AccrualInfo memory) {
return _storage.liquidity.liquidityNodeWithAccrual(tick);
}
/**
* @inheritdoc ILiquidity
*/
function depositSharePrice(uint128 tick) external view returns (uint256) {
return _unscale(_storage.liquidity.depositSharePrice(tick), false);
}
/**
* @inheritdoc ILiquidity
*/
function redemptionSharePrice(uint128 tick) external view returns (uint256) {
return _unscale(_storage.liquidity.redemptionSharePrice(tick), false);
}
/**************************************************************************/
/* Loan Receipt External Helpers */
/**************************************************************************/
/**
* @notice Decode loan receipt
* @param loanReceipt Loan receipt
* @return Decoded loan receipt
*/
function decodeLoanReceipt(bytes calldata loanReceipt) external pure returns (LoanReceipt.LoanReceiptV2 memory) {
return BorrowLogic._decodeLoanReceipt(loanReceipt);
}
/**************************************************************************/
/* Helper Functions */
/**************************************************************************/
/**
* @notice Helper function that returns underlying collateral in (address,
* uint256[], uint256) shape
* @param collateralToken Collateral token, either underlying token or collateral wrapper
* @param collateralTokenId Collateral token ID
* @param collateralWrapperContext Collateral wrapper context
* @return token Underlying collateral token
* @return tokenIds Underlying collateral token IDs (unique)
* @return tokenIdQuantities Underlying collateral token ID quantities
* @return tokenCount Underlying total token count
*/
function _getUnderlyingCollateral(
address collateralToken,
uint256 collateralTokenId,
bytes memory collateralWrapperContext
)
internal
view
returns (address token, uint256[] memory tokenIds, uint256[] memory tokenIdQuantities, uint256 tokenCount)
{
/* Enumerate if collateral token is a collateral wrapper */
if (
collateralToken == _collateralWrapper1 ||
collateralToken == _collateralWrapper2 ||
collateralToken == _collateralWrapper3
) {
(token, tokenIds, tokenIdQuantities) = ICollateralWrapper(collateralToken).enumerateWithQuantities(
collateralTokenId,
collateralWrapperContext
);
tokenCount = ICollateralWrapper(collateralToken).count(collateralTokenId, collateralWrapperContext);
return (token, tokenIds, tokenIdQuantities, tokenCount);
}
/* If single asset, convert to length one token ID array */
token = collateralToken;
tokenIds = new uint256[](1);
tokenIds[0] = collateralTokenId;
tokenIdQuantities = new uint256[](1);
tokenIdQuantities[0] = 1;
tokenCount = 1;
}
/**
* @notice Get reference to ERC-7201 delegate storage
* @return $ Reference to delegate storage
*/
function _getDelegateStorage() private pure returns (DelegateStorage storage $) {
assembly {
$.slot := DELEGATE_STORAGE_LOCATION
}
}
/**
* @dev Helper function to quote a loan
* @param principal Principal amount in currency tokens
* @param duration Duration in seconds
* @param collateralToken_ Collateral token address
* @param collateralTokenId Collateral token ID
* @param ticks Liquidity node ticks
* @param collateralWrapperContext Collateral wrapper context
* @param collateralFilterContext Collateral filter context
* @param oracleContext Oracle context
* @param isRefinance True if called by refinance()
* @return Repayment amount in currency tokens, admin fee in currency
* tokens, liquidity nodes, liquidity node count
*/
function _quote(
uint256 principal,
uint64 duration,
address collateralToken_,
uint256 collateralTokenId,
uint128[] calldata ticks,
bytes memory collateralWrapperContext,
bytes calldata collateralFilterContext,
bytes calldata oracleContext,
bool isRefinance
) internal view returns (uint256, uint256, LiquidityLogic.NodeSource[] memory, uint16) {
/* Get underlying collateral */
(
address underlyingCollateralToken,
uint256[] memory underlyingCollateralTokenIds,
uint256[] memory underlyingQuantities,
uint256 underlyingCollateralTokenCount
) = _getUnderlyingCollateral(collateralToken_, collateralTokenId, collateralWrapperContext);
/* Verify collateral is supported */
if (!isRefinance) {
for (uint256 i; i < underlyingCollateralTokenIds.length; i++) {
if (
!_collateralSupported(
underlyingCollateralToken,
underlyingCollateralTokenIds[i],
i,
collateralFilterContext
)
) revert UnsupportedCollateral(i);
}
}
/* Cache durations */
uint64[] memory durations_ = _storage.durations;
/* Validate duration */
if (duration > durations_[0]) revert UnsupportedLoanDuration();
/* Lookup duration index */
uint256 durationIndex = durations_.length - 1;
for (; durationIndex > 0; durationIndex--) {
if (duration <= durations_[durationIndex]) break;
}
/* Get oracle price if price oracle exists, else 0 */
uint256 oraclePrice = price(
collateralToken(),
address(_storage.currencyToken),
underlyingCollateralTokenIds,
underlyingQuantities,
oracleContext
);
/* Source liquidity nodes */
(LiquidityLogic.NodeSource[] memory nodes, uint16 count) = _storage.liquidity.source(
principal,
ticks,
underlyingCollateralTokenCount,
durationIndex,
_scale(oraclePrice)
);
/* Price interest for liquidity nodes */
(uint256 repayment, uint256 adminFee) = _price(
principal,
duration,
nodes,
count,
_storage.rates,
_storage.adminFeeRate
);
return (repayment, adminFee, nodes, count);
}
/**
* @dev Helper function to get currency token scaling factor
* @return Factor
*/
function _scaleFactor() internal view returns (uint256) {
return 10 ** (18 - IERC20Metadata(address(_storage.currencyToken)).decimals());
}
/**
* @dev Helper function to scale up a value
* @param value Value
* @return Scaled value
*/
function _scale(uint256 value) internal view returns (uint256) {
return value * _scaleFactor();
}
/**
* @dev Helper function to scale down a value
* @param value Value
* @param isRoundUp Round up if true
* @return Unscaled value
*/
function _unscale(uint256 value, bool isRoundUp) internal view returns (uint256) {
uint256 factor = _scaleFactor();
return (value % factor == 0 || !isRoundUp) ? value / factor : value / factor + 1;
}
/**************************************************************************/
/* Lend API */
/**************************************************************************/
/**
* @inheritdoc IPool
*/
function quote(
uint256 principal,
uint64 duration,
address collateralToken,
uint256 collateralTokenId,
uint128[] calldata ticks,
bytes calldata options
) external view returns (uint256) {
/* Quote repayment */
(uint256 repayment, , , ) = _quote(
_scale(principal),
duration,
collateralToken,
collateralTokenId,
ticks,
BorrowLogic._getOptionsData(options, BorrowOptions.CollateralWrapperContext),
BorrowLogic._getOptionsData(options, BorrowOptions.CollateralFilterContext),
BorrowLogic._getOptionsData(options, BorrowOptions.OracleContext),
false
);
return _unscale(repayment, true);
}
/**
* @inheritdoc IPool
*/
function borrow(
uint256 principal,
uint64 duration,
address collateralToken,
uint256 collateralTokenId,
uint256 maxRepayment,
uint128[] calldata ticks,
bytes calldata options
) external nonReentrant returns (uint256) {
uint256 scaledPrincipal = _scale(principal);
/* Quote repayment, admin fee, and liquidity nodes */
(uint256 repayment, uint256 adminFee, LiquidityLogic.NodeSource[] memory nodes, uint16 count) = _quote(
scaledPrincipal,
duration,
collateralToken,
collateralTokenId,
ticks,
BorrowLogic._getOptionsData(options, BorrowOptions.CollateralWrapperContext),
BorrowLogic._getOptionsData(options, BorrowOptions.CollateralFilterContext),
BorrowLogic._getOptionsData(options, BorrowOptions.OracleContext),
false
);
/* Handle borrow accounting */
(bytes memory encodedLoanReceipt, bytes32 loanReceiptHash) = BorrowLogic._borrow(
_storage,
scaledPrincipal,
duration,
collateralToken,
collateralTokenId,
repayment,
_scale(maxRepayment),
adminFee,
nodes,
count,
BorrowLogic._getOptionsData(options, BorrowOptions.CollateralWrapperContext)
);
/* Handle delegate.cash option */
BorrowLogic._optionDelegateCash(
_getDelegateStorage(),
collateralToken,
collateralTokenId,
_delegateRegistryV1,
_delegateRegistryV2,
options
);
/* Transfer collateral from borrower to pool */
IERC721(collateralToken).transferFrom(msg.sender, address(this), collateralTokenId);
/* Transfer principal from pool to borrower */
_storage.currencyToken.safeTransfer(msg.sender, principal);
/* Emit LoanOriginated */
emit LoanOriginated(loanReceiptHash, encodedLoanReceipt);
return _unscale(repayment, true);
}
/**
* @inheritdoc IPool
*/
function repay(bytes calldata encodedLoanReceipt) external nonReentrant returns (uint256) {
/* Handle repay accounting */
(uint256 repayment, LoanReceipt.LoanReceiptV2 memory loanReceipt, bytes32 loanReceiptHash) = BorrowLogic._repay(
_storage,
encodedLoanReceipt
);
uint256 unscaledRepayment = _unscale(repayment, true);
/* Revoke delegates */
BorrowLogic._revokeDelegates(
_getDelegateStorage(),
loanReceipt.collateralToken,
loanReceipt.collateralTokenId,
_delegateRegistryV1,
_delegateRegistryV2
);
/* Transfer repayment from borrower to pool */
_storage.currencyToken.safeTransferFrom(loanReceipt.borrower, address(this), unscaledRepayment);
/* Transfer collateral from pool to borrower */
IERC721(loanReceipt.collateralToken).transferFrom(
address(this),
loanReceipt.borrower,
loanReceipt.collateralTokenId
);
/* Emit Loan Repaid */
emit LoanRepaid(loanReceiptHash, unscaledRepayment);
return unscaledRepayment;
}
/**
* @inheritdoc IPool
*/
function refinance(
bytes calldata encodedLoanReceipt,
uint256 principal,
uint64 duration,
uint256 maxRepayment,
uint128[] calldata ticks,
bytes calldata options
) external nonReentrant returns (uint256) {
uint256 scaledPrincipal = _scale(principal);
/* Handle repay accounting */
(uint256 repayment, LoanReceipt.LoanReceiptV2 memory loanReceipt, bytes32 loanReceiptHash) = BorrowLogic._repay(
_storage,
encodedLoanReceipt
);
uint256 unscaledRepayment = _unscale(repayment, true);
/* Quote new repayment, admin fee, and liquidity nodes */
(uint256 newRepayment, uint256 adminFee, LiquidityLogic.NodeSource[] memory nodes, uint16 count) = _quote(
scaledPrincipal,
duration,
loanReceipt.collateralToken,
loanReceipt.collateralTokenId,
ticks,
loanReceipt.collateralWrapperContext,
encodedLoanReceipt[0:0],
BorrowLogic._getOptionsData(options, BorrowOptions.OracleContext),
true
);
/* Handle borrow accounting */
(bytes memory newEncodedLoanReceipt, bytes32 newLoanReceiptHash) = BorrowLogic._borrow(
_storage,
scaledPrincipal,
duration,
loanReceipt.collateralToken,
loanReceipt.collateralTokenId,
newRepayment,
_scale(maxRepayment),
adminFee,
nodes,
count,
loanReceipt.collateralWrapperContext
);
/* Determine transfer direction */
if (principal < unscaledRepayment) {
/* Transfer prorated repayment less principal from borrower to pool */
_storage.currencyToken.safeTransferFrom(loanReceipt.borrower, address(this), unscaledRepayment - principal);
} else {
/* Transfer principal less prorated repayment from pool to borrower */
_storage.currencyToken.safeTransfer(msg.sender, principal - unscaledRepayment);
}
/* Emit Loan Repaid */
emit LoanRepaid(loanReceiptHash, unscaledRepayment);
/* Emit LoanOriginated */
emit LoanOriginated(newLoanReceiptHash, newEncodedLoanReceipt);
return _unscale(newRepayment, true);
}
/**
* @inheritdoc IPool
*/
function liquidate(bytes calldata encodedLoanReceipt) external nonReentrant {
/* Handle liquidate accounting */
(LoanReceipt.LoanReceiptV2 memory loanReceipt, bytes32 loanReceiptHash) = BorrowLogic._liquidate(
_storage,
encodedLoanReceipt
);
/* Revoke delegates */
BorrowLogic._revokeDelegates(
_getDelegateStorage(),
loanReceipt.collateralToken,
loanReceipt.collateralTokenId,
_delegateRegistryV1,
_delegateRegistryV2
);
/* Approve collateral for transfer to _collateralLiquidator */
IERC721(loanReceipt.collateralToken).approve(address(_collateralLiquidator), loanReceipt.collateralTokenId);
/* Start liquidation with collateral liquidator */
_collateralLiquidator.liquidate(
address(_storage.currencyToken),
loanReceipt.collateralToken,
loanReceipt.collateralTokenId,
loanReceipt.collateralWrapperContext,
encodedLoanReceipt
);
/* Emit Loan Liquidated */
emit LoanLiquidated(loanReceiptHash);
}
/**************************************************************************/
/* Callbacks */
/**************************************************************************/
/**
* @inheritdoc ICollateralLiquidationReceiver
*/
function onCollateralLiquidated(bytes calldata encodedLoanReceipt, uint256 proceeds) external nonReentrant {
/* Validate caller is collateral liquidator */
if (msg.sender != address(_collateralLiquidator)) revert InvalidCaller();
/* Handle collateral liquidation accounting */
(uint256 borrowerSurplus, LoanReceipt.LoanReceiptV2 memory loanReceipt, bytes32 loanReceiptHash) = BorrowLogic
._onCollateralLiquidated(_storage, encodedLoanReceipt, _scale(proceeds));
uint256 unscaledBorrowerSurplus = _unscale(borrowerSurplus, false);
/* Transfer surplus to borrower */
if (unscaledBorrowerSurplus != 0)
IERC20(_storage.currencyToken).safeTransfer(loanReceipt.borrower, unscaledBorrowerSurplus);
/* Emit Collateral Liquidated */
emit CollateralLiquidated(loanReceiptHash, proceeds, unscaledBorrowerSurplus);
}
/**************************************************************************/
/* Deposit API */
/**************************************************************************/
/**
* @inheritdoc IPool
*/
function deposit(uint128 tick, uint256 amount, uint256 minShares) external nonReentrant returns (uint256) {
/* Handle deposit accounting and compute shares */
uint128 shares = DepositLogic._deposit(_storage, tick, _scale(amount).toUint128(), minShares.toUint128());
/* Call token hook */
_onExternalTransfer(address(0), msg.sender, tick, shares);
/* Transfer deposit amount */
_storage.currencyToken.safeTransferFrom(msg.sender, address(this), amount);
/* Emit Deposited */
emit Deposited(msg.sender, tick, amount, shares);
return shares;
}
/**
* @inheritdoc IPool
*/
function redeem(uint128 tick, uint256 shares) external nonReentrant returns (uint128) {
/* Handle redeem accounting */
uint128 redemptionId = DepositLogic._redeem(_storage, tick, shares.toUint128());
/* Call token hook */
_onExternalTransfer(msg.sender, address(0), tick, shares);
/* Emit Redeemed event */
emit Redeemed(msg.sender, tick, redemptionId, shares);
return redemptionId;
}
/**
* @inheritdoc IPool
*/
function redemptionAvailable(
address account,
uint128 tick,
uint128 redemptionId
) external view returns (uint256, uint256, uint256) {
/* Handle redemption available accounting */
(uint256 shares, uint256 amount, uint256 sharesAhead) = DepositLogic._redemptionAvailable(
_storage,
account,
tick,
redemptionId
);
return (shares, _unscale(amount, false), sharesAhead);
}
/**
* @inheritdoc IPool
*/
function withdraw(uint128 tick, uint128 redemptionId) external nonReentrant returns (uint256, uint256) {
/* Handle withdraw accounting and compute both shares and amount */
(uint128 shares, uint128 amount) = DepositLogic._withdraw(_storage, tick, redemptionId);
uint256 unscaledAmount = _unscale(amount, false);
/* Transfer withdrawal amount */
if (unscaledAmount != 0) _storage.currencyToken.safeTransfer(msg.sender, unscaledAmount);
/* Emit Withdrawn */
emit Withdrawn(msg.sender, tick, redemptionId, shares, unscaledAmount);
return (shares, unscaledAmount);
}
/**
* @inheritdoc IPool
*/
function rebalance(
uint128 srcTick,
uint128 dstTick,
uint128 redemptionId,
uint256 minShares
) external nonReentrant returns (uint256, uint256, uint256) {
/* Handle withdraw accounting and compute both shares and amount */
(uint128 oldShares, uint128 amount) = DepositLogic._withdraw(_storage, srcTick, redemptionId);
/* Handle deposit accounting and compute new shares */
uint128 newShares = DepositLogic._deposit(_storage, dstTick, amount, minShares.toUint128());
uint256 unscaledAmount = _unscale(amount, false);
/* Call token hook */
_onExternalTransfer(address(0), msg.sender, dstTick, newShares);
/* Emit Withdrawn */
emit Withdrawn(msg.sender, srcTick, redemptionId, oldShares, unscaledAmount);
/* Emit Deposited */
emit Deposited(msg.sender, dstTick, unscaledAmount, newShares);
return (oldShares, newShares, unscaledAmount);
}
/**
* @notice Transfer shares between accounts by operator
*
* @dev Only callable by deposit token contract
*
* @param from From
* @param to To
* @param tick Tick
* @param shares Shares
*/
function transfer(address from, address to, uint128 tick, uint256 shares) external nonReentrant {
/* Validate caller is deposit token created by Pool */
if (msg.sender != depositToken(tick)) revert InvalidCaller();
/* Handle transfer accounting */
DepositLogic._transfer(_storage, from, to, tick, shares.toUint128());
/* Emit Transferred */
emit Transferred(from, to, tick, shares);
}
/**
* @notice Tokenize a tick
*
* @param tick Tick
* @return Deposit token address
*/
function tokenize(uint128 tick) external returns (address) {
/* Validate tick */
Tick.validate(tick, 0, 0, _storage.durations.length - 1, 0, _storage.rates.length - 1);
return _tokenize(tick);
}
/**************************************************************************/
/* Admin Fees API */
/**************************************************************************/
/**
* @notice Set the admin fee rate
*
* Emits a {AdminFeeRateUpdated} event.
*
* @param rate Rate is the admin fee in basis points
*/
function setAdminFeeRate(uint32 rate) external {
BorrowLogic._setAdminFeeRate(_storage, rate);
emit AdminFeeRateUpdated(rate);
}
/**
* @notice Withdraw admin fees
*
* Emits a {AdminFeesWithdrawn} event.
*
* @param recipient Recipient account
* @param amount Amount to withdraw
*/
function withdrawAdminFees(address recipient, uint256 amount) external nonReentrant {
BorrowLogic._withdrawAdminFees(_storage, recipient, _scale(amount));
/* Transfer cash from Pool to recipient */
_storage.currencyToken.safeTransfer(recipient, amount);
emit AdminFeesWithdrawn(recipient, amount);
}
/******************************************************/
/* ERC165 interface */
/******************************************************/
/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return interfaceId == type(ICollateralLiquidationReceiver).interfaceId || super.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../LiquidityLogic.sol";
/**
* @title Interest Rate Model API
* @author MetaStreet Labs
*/
abstract contract InterestRateModel {
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Invalid parameters
*/
error InvalidInterestRateModelParameters();
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get interest rate model name
* @return Interest rate model name
*/
function INTEREST_RATE_MODEL_NAME() external view virtual returns (string memory);
/**
* @notice Get interest rate model version
* @return Interest rate model version
*/
function INTEREST_RATE_MODEL_VERSION() external view virtual returns (string memory);
/**
* @notice Price interest for liquidity
* @param principal Principal
* @param duration Duration
* @param nodes Liquidity nodes
* @param count Liquidity node count
* @param rates Interest rates
* @param adminFeeRate Admin fee rate
* @return repayment Repayment
* @return adminFee Admin fee
*/
function _price(
uint256 principal,
uint64 duration,
LiquidityLogic.NodeSource[] memory nodes,
uint16 count,
uint64[] memory rates,
uint32 adminFeeRate
) internal view virtual returns (uint256 repayment, uint256 adminFee);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./InterestRateModel.sol";
import "../Tick.sol";
/**
* @title Weighted Interest Rate Model
* @author MetaStreet Labs
*/
contract WeightedInterestRateModel is InterestRateModel {
using SafeCast for uint256;
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice WeightedInterestRateModel constructor
*/
constructor() {}
/**************************************************************************/
/* Implementation */
/**************************************************************************/
/**
* @inheritdoc InterestRateModel
*/
function INTEREST_RATE_MODEL_NAME() external pure override returns (string memory) {
return "WeightedInterestRateModel";
}
/**
* @inheritdoc InterestRateModel
*/
function INTEREST_RATE_MODEL_VERSION() external pure override returns (string memory) {
return "2.0";
}
/**
* @inheritdoc InterestRateModel
*/
function _price(
uint256 principal,
uint64 duration,
LiquidityLogic.NodeSource[] memory nodes,
uint16 count,
uint64[] memory rates,
uint32 adminFeeRate
) internal pure override returns (uint256, uint256) {
/* First pass to compute repayment and weights */
uint256[] memory weights = new uint256[](count);
uint256 repayment;
uint256 normalization;
for (uint256 i; i < count; i++) {
/* Compute tick repayment */
(, , uint256 rateIndex, ) = Tick.decode(nodes[i].tick, LiquidityLogic.BASIS_POINTS_SCALE);
uint256 pending = nodes[i].used +
Math.mulDiv(nodes[i].used, rates[rateIndex] * duration, LiquidityLogic.FIXED_POINT_SCALE);
/* Update cumulative repayment */
repayment += pending;
/* Compute tick weight */
weights[i] = Math.mulDiv(repayment, pending, principal);
/* Accumulate weight for normalization */
normalization += weights[i];
}
/* Compute interest and admin fee */
uint256 interest = repayment - principal;
uint256 adminFee = (interest * adminFeeRate) / LiquidityLogic.BASIS_POINTS_SCALE;
/* Deduct admin fee from interest */
interest -= adminFee;
/* Second pass to assign weighted interest to ticks */
uint256 interestRemaining = interest;
for (uint256 i; i < count; i++) {
/* Compute weighted interest to tick */
uint256 weightedInterest = Math.mulDiv(interest, weights[i], normalization);
/* Assign node pending amount */
nodes[i].pending = nodes[i].used + weightedInterest.toUint128();
/* Track remaining interest */
interestRemaining -= weightedInterest;
}
/* Drop off remaining interest dust at lowest node */
if (interestRemaining != 0) nodes[0].pending += interestRemaining.toUint128();
return (repayment, adminFee);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title Tick
* @author MetaStreet Labs
*/
library Tick {
/*
* A tick encodes three conditions on liquidity: limit, duration, rate, and type.
* Limit is the maximum depth that liquidity sourced from the node can be
* used in. Duration is the maximum allowed duration for that liquidity.
* Rate is the interest rate associated with that liquidity. Duration and
* rates are encoded as indexes into predetermined, discrete tiers. Type is the
* type of limit, which could either be absolute or ratio-based.
*
* +---------------------------------------------------------------------+
* | 128 |
* +--------------------------------------|----------|----------|--------+
* | 120 | 3 | 3 | 2 |
* | Limit | Dur. Idx | Rate Idx | Type |
* +---------------------------------------------------------------------+
*
* Duration Index is ordered from longest duration to shortest, e.g. 30
* days, 14 days, 7 days.
*
* Rate Index is ordered from lowest rate to highest rate, e.g. 10%, 30%,
* 50%.
*/
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @notice Limit type
*/
enum LimitType {
Absolute,
Ratio
}
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Tick limit mask
*/
uint256 internal constant TICK_LIMIT_MASK = 0xffffffffffffffffffffffffffffff;
/**
* @notice Tick limit shift
*/
uint256 internal constant TICK_LIMIT_SHIFT = 8;
/**
* @notice Tick duration index mask
*/
uint256 internal constant TICK_DURATION_MASK = 0x7;
/**
* @notice Tick duration index shift
*/
uint256 internal constant TICK_DURATION_SHIFT = 5;
/**
* @notice Tick rate index mask
*/
uint256 internal constant TICK_RATE_MASK = 0x7;
/**
* @notice Tick rate index shift
*/
uint256 internal constant TICK_RATE_SHIFT = 2;
/**
* @notice Tick limit type mask
*/
uint256 internal constant TICK_LIMIT_TYPE_MASK = 0x3;
/**
* @notice Maximum number of durations supported
*/
uint256 internal constant MAX_NUM_DURATIONS = TICK_DURATION_MASK + 1;
/**
* @notice Maximum number of rates supported
*/
uint256 internal constant MAX_NUM_RATES = TICK_RATE_MASK + 1;
/**
* @notice Basis points scale
*/
uint256 internal constant BASIS_POINTS_SCALE = 10_000;
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice Invalid tick
*/
error InvalidTick();
/**************************************************************************/
/* Helper Functions */
/**************************************************************************/
/**
* @dev Decode a Tick
* @param tick Tick
* @param oraclePrice Oracle price
* @return limit Limit field
* @return duration Duration field
* @return rate Rate field
* @return limitType Limit type field
*/
function decode(
uint128 tick,
uint256 oraclePrice
) internal pure returns (uint256 limit, uint256 duration, uint256 rate, LimitType limitType) {
limit = ((tick >> TICK_LIMIT_SHIFT) & TICK_LIMIT_MASK);
duration = ((tick >> TICK_DURATION_SHIFT) & TICK_DURATION_MASK);
rate = ((tick >> TICK_RATE_SHIFT) & TICK_RATE_MASK);
limitType = tick == type(uint128).max ? LimitType.Absolute : LimitType(tick & TICK_LIMIT_TYPE_MASK);
limit = limitType == LimitType.Ratio ? Math.mulDiv(oraclePrice, limit, BASIS_POINTS_SCALE) : limit;
}
/**
* @dev Validate a Tick (fast)
* @param tick Tick
* @param prevTick Previous tick
* @param maxDurationIndex Maximum Duration Index (inclusive)
* @param oraclePrice Oracle price
* @return Limit field
*/
function validate(
uint128 tick,
uint128 prevTick,
uint256 maxDurationIndex,
uint256 oraclePrice
) internal pure returns (uint256) {
(uint256 prevLimit, uint256 prevDuration, uint256 prevRate, ) = decode(prevTick, oraclePrice);
(uint256 limit, uint256 duration, uint256 rate, ) = decode(tick, oraclePrice);
if (limit < prevLimit) revert InvalidTick();
if (limit == prevLimit && (duration <= prevDuration || rate <= prevRate)) revert InvalidTick();
if (duration > maxDurationIndex) revert InvalidTick();
return limit;
}
/**
* @dev Validate a Tick (slow)
* @param tick Tick
* @param minLimit Minimum Limit (exclusive)
* @param minDurationIndex Minimum Duration Index (inclusive)
* @param maxDurationIndex Maximum Duration Index (inclusive)
* @param minRateIndex Minimum Rate Index (inclusive)
* @param maxRateIndex Maximum Rate Index (inclusive)
*/
function validate(
uint128 tick,
uint256 minLimit,
uint256 minDurationIndex,
uint256 maxDurationIndex,
uint256 minRateIndex,
uint256 maxRateIndex
) internal pure {
(uint256 limit, uint256 duration, uint256 rate, LimitType limitType) = decode(tick, BASIS_POINTS_SCALE);
if (limit <= minLimit) revert InvalidTick();
if (duration < minDurationIndex) revert InvalidTick();
if (duration > maxDurationIndex) revert InvalidTick();
if (rate < minRateIndex) revert InvalidTick();
if (rate > maxRateIndex) revert InvalidTick();
if (limitType == LimitType.Ratio && limit > BASIS_POINTS_SCALE) revert InvalidTick();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Deposit Token API
* @author MetaStreet Labs
*/
abstract contract DepositToken {
/**************************************************************************/
/* Events */
/**************************************************************************/
/**
* @notice Emitted when deposit token created
* @param instance Instance address
* @param implementation Implementation address
* @param tick Tick
*/
event TokenCreated(address indexed instance, address indexed implementation, uint128 indexed tick);
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get the deposit token address for tick
*
* @param tick Tick
* @return Deposit token address
*/
function depositToken(uint128 tick) public view virtual returns (address);
/**
* @notice Tokenize a tick
*
* @param tick Tick
* @return Deposit token address
*/
function _tokenize(uint128 tick) internal virtual returns (address);
/**
* @notice Hook called by Pool on token transfers
*
* @param from From
* @param to To
* @param tick Tick
* @param shares Shares
*/
function _onExternalTransfer(address from, address to, uint128 tick, uint256 shares) internal virtual;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "./DepositToken.sol";
import "./ERC20DepositTokenFactory.sol";
import "./ERC20DepositTokenImplementation.sol";
/**
* @title ERC20 Deposit Token
* @author MetaStreet Labs
*/
contract ERC20DepositToken is DepositToken {
/**************************************************************************/
/* Structures */
/**************************************************************************/
/**
* @custom:storage-location erc7201:erc20DepositToken.depositTokenStorage
*/
struct DepositTokenStorage {
/* Mapping of tick to token address */
mapping(uint128 => address) tokens;
}
/**************************************************************************/
/* State */
/**************************************************************************/
/**
* @notice Current ERC20 deposit token implementation
*/
address internal immutable _implementation;
/**
* @notice Deposit token storage slot
* @dev keccak256(abi.encode(uint256(keccak256("erc20DepositToken.depositTokenStorage")) - 1)) & ~bytes32(uint256(0xff));
*/
bytes32 private constant DEPOSIT_TOKEN_STORAGE_LOCATION =
0xc61d9ab4916a5eab6b572dc8707662b99e55e17ecdc61af8ff79465ad64ded00;
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice ERC20DepositToken constructor
*
* @param implementation_ ERC20 deposit token implementation address
*/
constructor(address implementation_) {
_implementation = implementation_;
}
/**************************************************************************/
/* Internal Helpers */
/**************************************************************************/
/**
* @notice Get reference to ERC-7201 deposit token storage
*
* @return $ Reference to deposit token storage
*/
function _getDepositTokenStorage() private pure returns (DepositTokenStorage storage $) {
assembly {
$.slot := DEPOSIT_TOKEN_STORAGE_LOCATION
}
}
/**************************************************************************/
/* API */
/**************************************************************************/
/**
* @notice Get ERC20 Deposit Token implementation address
*
* @return ERC20 Deposit Token implementation address
*/
function getERC20DepositTokenImplementation() external view returns (address) {
return _implementation;
}
/**
* @notice Tokenize a tick
*
* @param tick Tick
*/
function _tokenize(uint128 tick) internal override returns (address) {
/* Return token if it already exists */
address tokenInstance = depositToken(tick);
if (tokenInstance != address(0)) {
emit TokenCreated(tokenInstance, _implementation, tick);
return tokenInstance;
}
/* Create proxied token */
tokenInstance = ERC20DepositTokenFactory.deploy(tick);
/* Store token instance in mapping */
_getDepositTokenStorage().tokens[tick] = tokenInstance;
emit TokenCreated(tokenInstance, _implementation, tick);
return tokenInstance;
}
/**
* @inheritdoc DepositToken
*/
function depositToken(uint128 tick) public view override returns (address) {
return _getDepositTokenStorage().tokens[tick];
}
/**
* @inheritdoc DepositToken
*/
function _onExternalTransfer(address from, address to, uint128 tick, uint256 shares) internal override {
/* No operation if token does not exist */
if (depositToken(tick) == address(0)) return;
/* Call external transfer hook */
ERC20DepositTokenImplementation(depositToken(tick)).onExternalTransfer(from, to, shares);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/Create2.sol";
import "./ERC20DepositTokenProxy.sol";
/**
* @title ERC20 Deposit Token Factory
* @author MetaStreet Labs
*/
library ERC20DepositTokenFactory {
/**
* @notice Deploy a proxied ERC20 deposit token
* @param tick Tick
* @return Proxy address
*/
function deploy(uint128 tick) external returns (address) {
/* Create init data */
bytes memory initData = abi.encode(
address(this),
abi.encodeWithSignature("initialize(bytes)", abi.encode(tick))
);
/* Create token instance */
return
Create2.deploy(
0,
bytes32(uint256(tick)),
abi.encodePacked(type(ERC20DepositTokenProxy).creationCode, initData)
);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import "@openzeppelin/contracts/interfaces/IERC721Metadata.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "../Pool.sol";
import "../interfaces/ILiquidity.sol";
/**
* @title ERC20 Deposit Token Implementation
* @author MetaStreet Labs
*/
contract ERC20DepositTokenImplementation is IERC20Metadata {
using Tick for uint128;
using SafeCast for uint256;
/**************************************************************************/
/* Errors */
/**************************************************************************/
/**
* @notice ERC20 Errors from OpenZeppelin implementation:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/interfaces/draft-IERC6093.sol
*/
/**
* @notice Insufficient balance
*
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @notice Insufficient allowance
*
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @notice Invalid spender
*
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSpender(address sender);
/**
* @notice Invalid Sender
*
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @notice Invalid Receiver
*
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @notice Invalid caller
*/
error InvalidCaller();
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Implementation version
*/
string public constant IMPLEMENTATION_VERSION = "1.0";
/**
* @notice Fixed point scale
*/
uint256 internal constant FIXED_POINT_SCALE = 1e18;
/**************************************************************************/
/* State */
/**************************************************************************/
/**
* @notice Initialized boolean
*/
bool internal _initialized;
/**
* @notice MetaStreet V2 Pool
*/
Pool internal _pool;
/**
* @notice Deposit tick
*/
uint128 internal _tick;
/**
* @notice Owner => operator => allowance
*/
mapping(address => mapping(address => uint256)) private _allowances;
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice ERC20 Deposit Token Implementation constructor
*/
constructor() {
/* Disable initialization of implementation contract */
_initialized = true;
}
/**************************************************************************/
/* Initializer */
/**************************************************************************/
/**
* @notice Initializer
* @param params ABI-encoded parameters
*/
function initialize(bytes memory params) external {
require(!_initialized, "Already initialized");
_initialized = true;
/* Decode parameters */
uint128 tick_ = abi.decode(params, (uint128));
_pool = Pool(msg.sender);
_tick = tick_;
}
/**************************************************************************/
/* Internal Helpers */
/**************************************************************************/
/**
* @notice Helper function to get rounded loan limit for name() and symbol()
*
* @dev Solely utilized to generate rounded number in name() and symbol() getters.
* For absolute limit type, loan limits > 1 ETH are rounded to the nearest
* whole number. Under 1 ETH are rounded to the nearest hundredth place.
* For ratio limit type, loan limits are expressed as either a whole number
* percentage or as a 2 d.p percentage.
*
* @param loanLimit_ Loan limit as uint256
*
* @return Loan limit as string
*/
function _getLoanLimit(Tick.LimitType limitType_, uint256 loanLimit_) internal pure returns (string memory) {
/* If limit type is ratio, express loan limit as a percentage */
if (limitType_ == Tick.LimitType.Ratio) {
/* Compute integer and decimals */
string memory integer = Strings.toString(loanLimit_ / 100);
uint256 decimals_ = loanLimit_ % 100;
return
decimals_ == 0
? string.concat(integer, "%")
: string.concat(integer, ".", Strings.toString(decimals_), "%");
}
/* Handle loan limits > 1 ETH */
if (loanLimit_ >= FIXED_POINT_SCALE) {
return Strings.toString((loanLimit_ + (FIXED_POINT_SCALE / 2)) / FIXED_POINT_SCALE);
} else {
/* Handle loan limits < 1 ETH */
uint256 scaledValue = loanLimit_ * 100;
uint256 integer = scaledValue / FIXED_POINT_SCALE;
if (scaledValue % FIXED_POINT_SCALE >= FIXED_POINT_SCALE / 2) {
integer += 1;
}
uint256 hundredthPlaces = integer % 100;
string memory decimalStr = hundredthPlaces < 10
? string.concat("0", Strings.toString(hundredthPlaces))
: Strings.toString(hundredthPlaces);
return string.concat("0.", decimalStr);
}
}
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* @inheritdoc IERC20Metadata
*/
function name() public view returns (string memory) {
(uint256 limit_, , , Tick.LimitType limitType_) = _tick.decode(Tick.BASIS_POINTS_SCALE);
return
string.concat(
"MetaStreet V2 Deposit: ",
IERC721Metadata(_pool.collateralToken()).symbol(),
"-",
IERC20Metadata(_pool.currencyToken()).symbol(),
":",
_getLoanLimit(limitType_, limit_)
);
}
/**
* @inheritdoc IERC20Metadata
*/
function symbol() public view returns (string memory) {
(uint256 limit_, , , Tick.LimitType limitType_) = _tick.decode(Tick.BASIS_POINTS_SCALE);
return
string.concat(
"m",
IERC20Metadata(_pool.currencyToken()).symbol(),
"-",
IERC721Metadata(_pool.collateralToken()).symbol(),
":",
_getLoanLimit(limitType_, limit_)
);
}
/**
* @inheritdoc IERC20Metadata
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @notice Pool
* @return Pool address
*/
function pool() external view returns (Pool) {
return _pool;
}
/**
* @notice Tick
* @return Encoded tick
*/
function tick() external view returns (uint128) {
return _tick;
}
/**
* @notice Tick loan limit
* @return Loan limit in currency tokens
*/
function limit() external view returns (uint128) {
(uint256 limit_, , , ) = _tick.decode(Tick.BASIS_POINTS_SCALE);
return limit_.toUint128();
}
/**
* @notice Tick duration
* @return Duration in seconds
*/
function duration() external view returns (uint64) {
(, uint256 durationIndex, , ) = _tick.decode(Tick.BASIS_POINTS_SCALE);
return _pool.durations()[durationIndex];
}
/**
* @notice Tick rate
* @return Rate in interest per second
*/
function rate() external view returns (uint64) {
(, , uint256 rateIndex, ) = _tick.decode(Tick.BASIS_POINTS_SCALE);
return _pool.rates()[rateIndex];
}
/**
* @notice Currency token
* @return Address of currency token
*/
function currencyToken() external view returns (address) {
return _pool.currencyToken();
}
/**
* @notice Deposit share price
* @return Deposit share price
*/
function depositSharePrice() external view returns (uint256) {
return _pool.depositSharePrice(_tick);
}
/**
* @notice Redemption share price
* @return Redemption share price
*/
function redemptionSharePrice() external view returns (uint256) {
return _pool.redemptionSharePrice(_tick);
}
/**************************************************************************/
/* Internal Helpers */
/**************************************************************************/
/**
* @notice Helper function to transfer tokens
*
* @param from From
* @param to To
* @param value Value
*/
function _transfer(address from, address to, uint256 value) internal {
/* No transfer to zero address */
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
/* Validate balance */
uint256 fromBalance = balanceOf(from);
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
/* Call transfer on pool */
_pool.transfer(from, to, _tick, value);
emit Transfer(from, to, value);
}
/**************************************************************************/
/* Hooks */
/**************************************************************************/
/**
* @notice External transfer hook
*
* @param from From
* @param to To
* @param value Value
*/
function onExternalTransfer(address from, address to, uint256 value) external {
if (msg.sender != address(_pool)) revert InvalidCaller();
emit Transfer(from, to, value);
}
/**************************************************************************/
/* IERC20 API */
/**************************************************************************/
/**
* @inheritdoc IERC20
*/
function totalSupply() public view returns (uint256) {
/* Get Pool node */
ILiquidity.NodeInfo memory node = _pool.liquidityNode(_tick);
/* Calculate total supply */
return node.shares - node.redemptions;
}
/**
* @inheritdoc IERC20
*/
function balanceOf(address account) public view returns (uint256) {
/* Get shares from deposits */
(uint128 shares, ) = _pool.deposits(account, _tick);
return shares;
}
/**
* @inheritdoc IERC20
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
/**
* @inheritdoc IERC20
*/
function approve(address spender, uint256 value) public returns (bool) {
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
/**
* @inheritdoc IERC20
*/
function transfer(address to, uint256 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
* @inheritdoc IERC20
*/
function transferFrom(address from, address to, uint256 value) public returns (bool) {
/* No transfer from zero address */
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
/* Check + update allowance */
uint256 currentAllowance = allowance(from, msg.sender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(msg.sender, currentAllowance, value);
}
unchecked {
_allowances[from][msg.sender] = currentAllowance - value;
}
}
_transfer(from, to, value);
return true;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "./ERC20DepositToken.sol";
/**
* @title ERC20 Deposit Token Proxy
* @author MetaStreet Labs
*/
contract ERC20DepositTokenProxy is Proxy {
/**************************************************************************/
/* Constants */
/**************************************************************************/
/**
* @notice Beacon address (ERC20DepositToken)
*/
address internal immutable _beacon;
/**************************************************************************/
/* Constructor */
/**************************************************************************/
/**
* @notice ERC20DepositTokenProxy constructor
*
* @dev Set the ERC20DepositToken address as beacon
* and initializes the storage of the Proxy
*
* @param beacon Beacon address
* @param data Initialization data
*/
constructor(address beacon, bytes memory data) {
_beacon = beacon;
Address.functionDelegateCall(ERC20DepositToken(beacon).getERC20DepositTokenImplementation(), data);
}
/**************************************************************************/
/* Getters */
/**************************************************************************/
/**
* @notice Get implementation address
*
* @dev Overrides Proxy._implementation()
*
* @return Implementation address
*/
function _implementation() internal view virtual override returns (address) {
return ERC20DepositToken(_beacon).getERC20DepositTokenImplementation();
}
}