ETH Price: $3,264.55 (+2.25%)
Gas: 1 Gwei

Contract

0x495679E784bDE3F88EedCEa4307d9a4f563aAd70
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x61016060197407312024-04-26 16:31:2391 days ago1714149083IN
 Contract Creation
0 ETH0.049866369.47431338

Advanced mode:
Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x56C5Aef1...dA409b53D
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
WeightedRateSetCollectionPool

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 400 runs

Other Settings:
shanghai EvmVersion
File 1 of 46 : WeightedRateSetCollectionPool.sol
// 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";
    }
}

File 2 of 46 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/extensions/IERC20Metadata.sol";

File 3 of 46 : IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../token/ERC721/extensions/IERC721Metadata.sol";

File 4 of 46 : Proxy.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 {}
}

File 5 of 46 : ReentrancyGuard.sol
// 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;
    }
}

File 6 of 46 : IERC20Metadata.sol
// 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);
}

File 7 of 46 : IERC20Permit.sol
// 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);
}

File 8 of 46 : IERC20.sol
// 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);
}

File 9 of 46 : SafeERC20.sol
// 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));
    }
}

File 10 of 46 : IERC721Metadata.sol
// 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);
}

File 11 of 46 : IERC721.sol
// 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);
}

File 12 of 46 : Address.sol
// 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);
        }
    }
}

File 13 of 46 : Create2.sol
// 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)
        }
    }
}

File 14 of 46 : ERC165.sol
// 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;
    }
}

File 15 of 46 : IERC165.sol
// 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);
}

File 16 of 46 : Math.sol
// 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);
        }
    }
}

File 17 of 46 : SafeCast.sol
// 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);
    }
}

File 18 of 46 : SignedMath.sol
// 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);
        }
    }
}

File 19 of 46 : Multicall.sol
// 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;
    }
}

File 20 of 46 : Strings.sol
// 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));
    }
}

File 21 of 46 : EnumerableSet.sol
// 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;
    }
}

File 22 of 46 : BorrowLogic.sol
// 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;
    }
}

File 23 of 46 : DepositLogic.sol
// 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;
    }
}

File 24 of 46 : CollateralFilter.sol
// 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);
}

File 25 of 46 : SetCollectionCollateralFilter.sol
// 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);
    }
}

File 26 of 46 : IDelegateRegistryV1.sol
// 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);
}

File 27 of 46 : IDelegateRegistryV2.sol
// 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);
}

File 28 of 46 : ICollateralLiquidationReceiver.sol
// 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;
}

File 29 of 46 : ICollateralLiquidator.sol
// 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;
}

File 30 of 46 : ICollateralWrapper.sol
// 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;
}

File 31 of 46 : ILiquidity.sol
// 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);
}

File 32 of 46 : IPool.sol
// 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;
}

File 33 of 46 : IPriceOracle.sol
// 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);
}

File 34 of 46 : LiquidityLogic.sol
// 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());
    }
}

File 35 of 46 : LoanReceipt.sol
// 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;
    }
}

File 36 of 46 : ExternalPriceOracle.sol
// 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;
    }
}

File 37 of 46 : PriceOracle.sol
// 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);
}

File 38 of 46 : Pool.sol
// 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.9";
    }

    /**
     * @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,
            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);
    }
}

File 39 of 46 : InterestRateModel.sol
// 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);
}

File 40 of 46 : WeightedInterestRateModel.sol
// 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);
    }
}

File 41 of 46 : Tick.sol
// 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();
    }
}

File 42 of 46 : DepositToken.sol
// 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;
}

File 43 of 46 : ERC20DepositToken.sol
// 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);
    }
}

File 44 of 46 : ERC20DepositTokenFactory.sol
// 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)
            );
    }
}

File 45 of 46 : ERC20DepositTokenImplementation.sol
// 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;
    }
}

File 46 of 46 : ERC20DepositTokenProxy.sol
// 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();
    }
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 400
  },
  "evmVersion": "shanghai",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/BorrowLogic.sol": {
      "BorrowLogic": "0x2184298d600b5f2ff2febca0993626581a994095"
    },
    "contracts/DepositLogic.sol": {
      "DepositLogic": "0x652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f"
    },
    "contracts/LiquidityLogic.sol": {
      "LiquidityLogic": "0xdd0694ed7ecb6494a9055a78cf848a6b146c88b5"
    },
    "contracts/tokenization/ERC20DepositTokenFactory.sol": {
      "ERC20DepositTokenFactory": "0xcc985298cc007901c9994920fecedadbeba8380f"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"collateralLiquidator","type":"address"},{"internalType":"address","name":"delegateRegistryV1","type":"address"},{"internalType":"address","name":"delegateRegistryV2","type":"address"},{"internalType":"address","name":"erc20DepositTokenImplementation","type":"address"},{"internalType":"address[]","name":"collateralWrappers","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InactiveLiquidity","type":"error"},{"inputs":[],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InsufficientShares","type":"error"},{"inputs":[],"name":"InsufficientTickSpacing","type":"error"},{"inputs":[],"name":"InvalidBorrowOptions","type":"error"},{"inputs":[],"name":"InvalidCaller","type":"error"},{"inputs":[],"name":"InvalidCollateralFilterParameters","type":"error"},{"inputs":[],"name":"InvalidInterestRateModelParameters","type":"error"},{"inputs":[],"name":"InvalidLoanReceipt","type":"error"},{"inputs":[],"name":"InvalidParameters","type":"error"},{"inputs":[],"name":"InvalidRedemptionStatus","type":"error"},{"inputs":[],"name":"InvalidTick","type":"error"},{"inputs":[],"name":"LoanNotExpired","type":"error"},{"inputs":[],"name":"RepaymentTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"UnsupportedCollateral","type":"error"},{"inputs":[],"name":"UnsupportedLoanDuration","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rate","type":"uint256"}],"name":"AdminFeeRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AdminFeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"loanReceiptHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"proceeds","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowerProceeds","type":"uint256"}],"name":"CollateralLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint128","name":"tick","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"loanReceiptHash","type":"bytes32"}],"name":"LoanLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"loanReceiptHash","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"loanReceipt","type":"bytes"}],"name":"LoanOriginated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"loanReceiptHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"repayment","type":"uint256"}],"name":"LoanRepaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint128","name":"tick","type":"uint128"},{"indexed":true,"internalType":"uint128","name":"redemptionId","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Redeemed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"instance","type":"address"},{"indexed":true,"internalType":"address","name":"implementation","type":"address"},{"indexed":true,"internalType":"uint128","name":"tick","type":"uint128"}],"name":"TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint128","name":"tick","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint128","name":"tick","type":"uint128"},{"indexed":true,"internalType":"uint128","name":"redemptionId","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"ABSOLUTE_TICK_LIMIT_SPACING_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"COLLATERAL_FILTER_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"COLLATERAL_FILTER_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"IMPLEMENTATION_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"IMPLEMENTATION_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"INTEREST_RATE_MODEL_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"INTEREST_RATE_MODEL_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"RATIO_TICK_LIMIT_SPACING_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"adminFeeBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"adminFeeRate","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint64","name":"duration","type":"uint64"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralTokenId","type":"uint256"},{"internalType":"uint256","name":"maxRepayment","type":"uint256"},{"internalType":"uint128[]","name":"ticks","type":"uint128[]"},{"internalType":"bytes","name":"options","type":"bytes"}],"name":"borrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collateralLiquidator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralTokenIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralWrappers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currencyToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"loanReceipt","type":"bytes"}],"name":"decodeLoanReceipt","outputs":[{"components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"repayment","type":"uint256"},{"internalType":"uint256","name":"adminFee","type":"uint256"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint64","name":"maturity","type":"uint64"},{"internalType":"uint64","name":"duration","type":"uint64"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralTokenId","type":"uint256"},{"internalType":"uint16","name":"collateralWrapperContextLen","type":"uint16"},{"internalType":"bytes","name":"collateralWrapperContext","type":"bytes"},{"components":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"used","type":"uint128"},{"internalType":"uint128","name":"pending","type":"uint128"}],"internalType":"struct LoanReceipt.NodeReceipt[]","name":"nodeReceipts","type":"tuple[]"}],"internalType":"struct LoanReceipt.LoanReceiptV2","name":"","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"delegationRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"delegationRegistryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minShares","type":"uint256"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"depositSharePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"depositToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"deposits","outputs":[{"internalType":"uint128","name":"shares","type":"uint128"},{"internalType":"uint128","name":"redemptionId","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"durations","outputs":[{"internalType":"uint64[]","name":"","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getERC20DepositTokenImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"params","type":"bytes"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedLoanReceipt","type":"bytes"}],"name":"liquidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"liquidityNode","outputs":[{"components":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"value","type":"uint128"},{"internalType":"uint128","name":"shares","type":"uint128"},{"internalType":"uint128","name":"available","type":"uint128"},{"internalType":"uint128","name":"pending","type":"uint128"},{"internalType":"uint128","name":"redemptions","type":"uint128"},{"internalType":"uint128","name":"prev","type":"uint128"},{"internalType":"uint128","name":"next","type":"uint128"}],"internalType":"struct ILiquidity.NodeInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"liquidityNodeWithAccrual","outputs":[{"components":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"value","type":"uint128"},{"internalType":"uint128","name":"shares","type":"uint128"},{"internalType":"uint128","name":"available","type":"uint128"},{"internalType":"uint128","name":"pending","type":"uint128"},{"internalType":"uint128","name":"redemptions","type":"uint128"},{"internalType":"uint128","name":"prev","type":"uint128"},{"internalType":"uint128","name":"next","type":"uint128"}],"internalType":"struct ILiquidity.NodeInfo","name":"","type":"tuple"},{"components":[{"internalType":"uint128","name":"accrued","type":"uint128"},{"internalType":"uint64","name":"rate","type":"uint64"},{"internalType":"uint64","name":"timestamp","type":"uint64"}],"internalType":"struct ILiquidity.AccrualInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"startTick","type":"uint128"},{"internalType":"uint128","name":"endTick","type":"uint128"}],"name":"liquidityNodes","outputs":[{"components":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"value","type":"uint128"},{"internalType":"uint128","name":"shares","type":"uint128"},{"internalType":"uint128","name":"available","type":"uint128"},{"internalType":"uint128","name":"pending","type":"uint128"},{"internalType":"uint128","name":"redemptions","type":"uint128"},{"internalType":"uint128","name":"prev","type":"uint128"},{"internalType":"uint128","name":"next","type":"uint128"}],"internalType":"struct ILiquidity.NodeInfo[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"receiptHash","type":"bytes32"}],"name":"loans","outputs":[{"internalType":"enum Pool.LoanStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedLoanReceipt","type":"bytes"},{"internalType":"uint256","name":"proceeds","type":"uint256"}],"name":"onCollateralLiquidated","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"address","name":"currencyToken","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenIdQuantities","type":"uint256[]"},{"internalType":"bytes","name":"oracleContext","type":"bytes"}],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint64","name":"duration","type":"uint64"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralTokenId","type":"uint256"},{"internalType":"uint128[]","name":"ticks","type":"uint128[]"},{"internalType":"bytes","name":"options","type":"bytes"}],"name":"quote","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rates","outputs":[{"internalType":"uint64[]","name":"","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"srcTick","type":"uint128"},{"internalType":"uint128","name":"dstTick","type":"uint128"},{"internalType":"uint128","name":"redemptionId","type":"uint128"},{"internalType":"uint256","name":"minShares","type":"uint256"}],"name":"rebalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"redemptionId","type":"uint128"}],"name":"redemptionAvailable","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"redemptionSharePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"redemptionId","type":"uint128"}],"name":"redemptions","outputs":[{"components":[{"internalType":"uint128","name":"pending","type":"uint128"},{"internalType":"uint128","name":"index","type":"uint128"},{"internalType":"uint128","name":"target","type":"uint128"}],"internalType":"struct Pool.Redemption","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedLoanReceipt","type":"bytes"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint64","name":"duration","type":"uint64"},{"internalType":"uint256","name":"maxRepayment","type":"uint256"},{"internalType":"uint128[]","name":"ticks","type":"uint128[]"},{"internalType":"bytes","name":"options","type":"bytes"}],"name":"refinance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedLoanReceipt","type":"bytes"}],"name":"repay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"rate","type":"uint32"}],"name":"setAdminFeeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"}],"name":"tokenize","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"tick","type":"uint128"},{"internalType":"uint128","name":"redemptionId","type":"uint128"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawAdminFees","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Deployed Bytecode

0x610200806040526004361015610013575f80fd5b5f90813560e01c908162a3fecd1461421b5750806301ffc9a7146141c45780630a81c2e7146141a757806312ac2e0a1461400e57806314a1ad1a14613f835780631721539b14613c6c5780631b1c740514613b745780631fbdd72d14613b30578063202d5c6b14613aec5780632630c12f14613aa65780632d896b2a14613a88578063439fab91146135dd57806343f48fbd146134ae5780634a41d89d146134865780635c4c16a0146133c7578063673d9f571461330757806369e73160146132ba5780636b2fa374146132935780636c2bb22d1461321d5780636df7c51414612a535780637241088714612187578063754b377c146121435780637e27c50f14611fde57806385dd920614611f9a5780638846a31b14611f565780638a2ecb4d14611de15780638cece52714611dba5780638f9f8daa14611d5c57806392e8519f14611d18578063951d4dd914611cba5780639d9705b814611b76578063a5615e3b14611b03578063a64eee2814611a60578063ac9615f014611444578063ac9650d81461129b578063ade0c18a14611069578063b2016bd414611042578063b4daa0e714610f90578063b6069ee514610e92578063b9c733b614610c07578063bad9920814610bba578063c4a9081514610b6d578063c72c2b8714610aa7578063cc0c314a14610987578063d4763de714610877578063e22ba7ae14610833578063e4674e0014610816578063e59adeaf146106d3578063e611ac6d1461057d578063e965a8ac146104e8578063f51a8811146102835763f851a4401461025a575f80fd5b3461028057806003193601126102805760206001600160a01b0360045416604051908152f35b80fd5b50346102805760803660031901126102805761029d614497565b6102a56144ad565b6102ad6144c3565b6102b56152b3565b73652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f9060405192637221bad760e11b8452600160048501526001600160801b0380809616928360248701521692836044860152604085606481845af49081156104dd57879588926104a2575b5090602061036a926103276064356154b1565b60405163284a271360e01b8152600160048201526001600160801b0380881660248301528086166044830152909116606482015293849190829081906084820190565b03915af4918215610497578892610443575b509186979161039261043f979860019516614c5e565b95898216956103a2878533615be0565b604080516001600160801b038c168152602081018a905233917f354770e7f865c755f9fd808af7814ca3995ae730bcf14887fb0b6d301b18f3f791a4604080518781526001600160801b039290921660208301529189169133917fd3d9500b000f4b80b8f9aea0e7ce7d135560d7775a63356999a9f3efd4f5edf89190a35560405194859416846040919493926060820195825260208201520152565b0390f35b9091506020813d821161048f575b8161045e602093836143e2565b8101031261048b579186979161039261043f979861047d60019661492c565b949a5098975050909261037c565b8780fd5b3d9150610451565b6040513d8a823e3d90fd5b6020965061036a92506104cc9060403d6040116104d6575b6104c481836143e2565b81019061551a565b9690969250610314565b503d6104ba565b6040513d89823e3d90fd5b50346102805760a03660031901126102805761050261453c565b61050a614568565b916001600160401b036044358181116105795761052b9036906004016145db565b90606435818111610575576105449036906004016145db565b9260843591821161028057602061056d878787876105653660048a016142be565b94909361595a565b604051908152f35b8380fd5b8280fd5b503461028057602036600319011261028057610597614497565b906105a06149dd565b506105a961490e565b5060405163d59efe0f60e01b8152600660048201526001600160801b03928316602482015261016092838260448173dd0694ed7ecb6494a9055a78cf848a6b146c88b55af49182156106c857838093610638575b505061060c60405180946144d9565b81511661010083015260406001600160401b039182602082015116610120850152015116610140820152f35b9192509250833d85116106c1575b61065081856143e2565b830190838203918583126106bd5761066a60609186614940565b9260ff19011261028057506106af6101406040519461068886614390565b610695610100820161492c565b86526106a46101208201614a1a565b602087015201614a1a565b604084015291905f806105fd565b5080fd5b503d610646565b6040513d85823e3d90fd5b5034610280576040366003190112610280576106ed614497565b6106f56144ad565b6106fd6152b3565b60405191637221bad760e11b8352600160048401526001600160801b038080921692836024860152169384604485015260408460648173652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f5af494851561080957604095829583916107da575b509061076d846001949316614c5e565b94856107bf575b87516001600160801b03881681526020810187905233907f354770e7f865c755f9fd808af7814ca3995ae730bcf14887fb0b6d301b18f3f790604090a4558351921682526020820152f35b6107d5866001600160a01b038654163390615307565b610774565b8496506001939291506107fc61076d91893d8b116104d6576104c481836143e2565b979097929394505061075d565b50604051903d90823e3d90fd5b503461028057806003193601126102805760206040516103e88152f35b503461028057806003193601126102805760206040516001600160a01b037f000000000000000000000000e0194f47040e2424b8a65cb5f7112a5dbe1f93bf168152f35b50346102805760603660031901126102805761089161453c565b6108996144ad565b6108a16144c3565b6001600160a01b0360405193631e82c22f60e11b8552600160048601521660248401526001600160801b03809216604484015216606482015260608160848173652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f5af491821561097b578091818094610934575b505061091661043f91614c5e565b92604051938493846040919493926060820195825260208201520152565b92509250506060823d8211610973575b81610951606093836143e2565b810103126102805750805160208201516040909201519161091661043f610908565b3d9150610944565b604051903d90823e3d90fd5b5034610280576040366003190112610280576109a1614497565b602435916109ad6152b3565b6109b6836154b1565b9060405193632edaf34360e21b8552600160048601526001600160801b038080861694856024890152166044870152602094858760648173652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f5af4968715610a9c578497610a5b575b5095610a2383600196979833615c66565b169384916040519081527f3ac2f453c81511b46084827fa6798fc6c8f7da31176e5be199525d49e59c7675873392a455604051908152f35b96508587813d8311610a95575b610a7281836143e2565b8101031261057557610a2383610a8b600197989961492c565b9897965050610a12565b503d610a68565b6040513d86823e3d90fd5b503461028057606036600319011261028057606090610ac461453c565b6040610ace6144ad565b926001600160a01b03610adf6144c3565b93610ae861490e565b501681526007602052206001600160801b038093165f5260205281600160405f200191165f5260205260405f2090600160405192610b2584614390565b8054838116855260801c60208501520154166040820152610b6b6040518092604090816001600160801b0391828151168552826020820151166020860152015116910152565bf35b5034610280576020366003190112610280576004358152600860205260ff604082205416604051906005811015610ba657602092508152f35b634e487b7160e01b83526021600452602483fd5b5034610280576020366003190112610280576001600160a01b0360406020926001600160801b03610be9614497565b1681525f80516020615cdd8339815191528452205416604051908152f35b503461028057602036600319011261028057600435816001600160401b03821161028057610c3c610c869236906004016142be565b9190610c466152b3565b732184298d600b5f2ff2febca0993626581a9940959260405194859283926394356ba560e01b845260016004850152604060248501526044840191614c13565b0381845af49081156106c857838081948294610e62575b50610ca790614cac565b9360e08101926001600160a01b039081855116946101008401958651833b15610e5e576040516334e9600560e01b81527ff0e5094ebd597f2042580340ce53d1b15e5b64e0d8be717ecde51dd37c61930060048201526001600160a01b03928316602482015260448101919091527f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b821660648201527f00000000000000000000000000000000000000447e69651d841bd8d104bed4939091166084820152918590839060a49082905af48015610e535783928691610e3a575b5050610d9c886080846001541696019584875116309161546f565b5116915116925190803b15610579576040516323b872dd60e01b81523060048201526001600160a01b0394909416602485015260448401919091528290606490829084905af18015610a9c57610e26575b506020926001917f1d7f9b884cc43b9a5c67bd7c6f6fb5ddb8cb3eb3994e934e8448d7bf4a4a25c985604051868152a255604051908152f35b610e308491614361565b610579575f610ded565b610e4691929350614361565b610575578190845f610d81565b6040513d87823e3d90fd5b8680fd5b9050610e86919450610ca793503d8086833e610e7e81836143e2565b81019061543f565b94919390949390610c9d565b503461028057604036600319011261028057610eac61453c565b60243590610eb86152b3565b732184298d600b5f2ff2febca0993626581a99409591610edf610ed9614cde565b82614f37565b92803b15610f815784604051637071977160e01b81526001600482015281816064816001600160a01b0396878a169a8b602484015260448301525af48015610f8557610f69575b505090610f5b817fcdcaff67ac16639664e5f9343c9223a1dc9c972ec367b69ae9fc1325c7be54749460209460015416615307565b604051908152a26001815580f35b610f7590939293614361565b610f815790845f610f26565b8480fd5b6040513d84823e3d90fd5b50346102805760203660031901126102805760043563ffffffff81168091036106bd5781732184298d600b5f2ff2febca0993626581a994095803b156106bd578160449160405192838092634692ee3b60e01b8252600160048301528760248301525af48015610f855761102e575b507f576be7f9615bb4c6e139635cf4380b1ce9f6aa425dabe6b9057c24a86cc3c979602083604051908152a180f35b61103790614361565b6106bd57815f610fff565b503461028057806003193601126102805760206001600160a01b0360095416604051908152f35b50346102805760209081600319360112610280576001600160401b039160043583811161057957906110a26111249236906004016142be565b9390604051946110b186614374565b828652828487015282604087015260609183838801528360808801528360a08801528360c08801528360e08801526101008481890152846101209281848b01526101409486868c015286610160809c0152604051998a92839263088f372160e21b84528b60048501526024840191614c13565b0381732184298d600b5f2ff2febca0993626581a9940955af4968715610e53578597611257575b50979695939492919061ffff90604051998a99868b5260ff885116878c01528688015160408c01526040880151868c01528588015160808c01526001600160a01b03908160808a01511660a08d01528060a08a01511660c08d015260c08901511660e08c015260e088015116818b0152860151818a01528501511681880152830151946111e56101809687838a01526101a0890190614299565b93015194601f198785030190870152818086519485815201950193905b83821061120f5786860387f35b9184965082866112476001949698849851604090816001600160801b0391828151168552826020820151166020860152015116910152565b0196019201869594929391611202565b9096503d8086833e61126981836143e2565b8101908681830312611297578051908a8211610e5e579161128f9161ffff949301614a95565b96909161114b565b8580fd5b5034610280576020806003193601126106bd576001600160401b0360043581811161057557906112d084923690600401614594565b90916112db826145c4565b926112e960405194856143e2565b828452601f196112f8846145c4565b01855b818110611435575050845b83811061136d575050505060405191838301848452825180915260408401948060408360051b870101940192955b8287106113415785850386f35b90919293828061135d600193603f198a82030186528851614299565b9601920196019592919092611334565b8060059796971b820135601e198336030181121561048b578201908135918483116114315787018236038113611431576113af61140e9161142994369161441e565b8980604051926113be84614390565b602784527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c8c850152660819985a5b195960ca1b60408501528b81519101305af4611407614668565b9030614697565b6114188288614654565b526114238187614654565b50614639565b959495611306565b8880fd5b606086820189015287016112fb565b50346102805760c03660031901126102805761145e61457e565b90611467614552565b916084356001600160401b03811161057957611487903690600401614594565b93909260a435906001600160401b03821161028057506114ab9036906004016142be565b9190946114fd6114c46114bc614cde565b600435614f37565b926114f36114e96114d5878b614e6a565b97909a6114e28282614eca565b5050614dcf565b999096369161441e565b9060643590614fda565b5f9893989792979691965b88518110156115505761152561151e828b614654565b518b615930565b156115385761153390614639565b611508565b60249060405190630d897fc560e11b82526004820152fd5b50908792918a61155e614734565b926001600160401b038061157186614647565b51169516948511611a4e5783515f199a908b81019081116119825794855b611a1d575b506115bb939495969798999a506001600160a01b03978860095416600154998a169061595a565b916115c5876145c4565b926115d360405194856143e2565b878452601f196115e2896145c4565b015f5b818110611a065750505f9485865b8a881080806119fc575b15611777571561176357600588901b8a01356001600160801b038116900361175f5783611629916155bf565b50919061163c868d8c60051b01356155bf565b509194909380861061172a578514928361173c575b50505061172a57851061172a57818a6001600160801b038e6116b284600696848f61169f8f9b61169a6117019d8560019560051b8d0135165f5260209d8e5260405f2096614f37565b614f2a565b9201541680821015611722575092614f2a565b8082101561171b57505b1680936001600160801b03604051936116d485614390565b8d60051b01351683528201525f60408201526116f08a8a614654565b526116fb8989614654565b50614c51565b966117128760051b8a013597614639565b969790976115f3565b90506116bc565b905092614f2a565b6040516333a3bdff60e21b8152600490fd5b841180159350909190611754575b50508e8080611651565b111590508e8061174a565b5f80fd5b634e487b7160e01b5f52603260045260245ffd5b50888884898f8087106119ea576020938481116119ea5761ffff9081811161199657169492906117a5614852565b936117af87614fa8565b915f95869487915b878a8c85106118b7575050505050906127106117e963ffffffff6117de6117f0958a614f2a565b9360a01c1683614f37565b0490614f2a565b91825f925b888410611846578761056d88888880611810575b5050614cac565b61182461181e6040926154b1565b92614647565b51019061183c6001600160801b039182845116615a3c565b1690528380611809565b909192936118a96118af916118668661185f8987614654565b51876157b2565b906001600160801b03611891818d61187e8c8e614654565b5101511661188b856154b1565b90615a3c565b90604061189e8b8d614654565b510191169052614f2a565b94614639565b9291906117f5565b909192939697846001600160401b036119136118e16118e98c6001600160801b039283918a614654565b51511661553b565b50979150506119088d83836118fe838d614654565b5101511699614654565b510151169487614654565b5116026001600160401b038116908103611982578661194f611948611978966119426119569561197298615631565b90614c51565b809e614c51565b9c8d6157b2565b611960898b614654565b5261196b888a614654565b5190614c51565b95614639565b91909594956117b7565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b815260048101879052602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608490fd5b60405163bb55fd2760e01b8152600490fd5b508c8314156115fd565b602090611a1161490e565b828289010152016115e5565b946001600160401b03611a308288614654565b5116871115611a48578015611982578b01948561158f565b94611594565b60405163b572bb8560e01b8152600490fd5b5034610280576020908160031936011261028057611a7c614497565b604051634ad37cf760e11b8152600660048201526001600160801b03909116602482015290828260448173dd0694ed7ecb6494a9055a78cf848a6b146c88b55af490811561097b5790611ad4575b61056d9150614c5e565b508181813d8311611afc575b611aea81836143e2565b8101031261175f5761056d9051611aca565b503d611ae0565b5034610280576020908160031936011261028057611b1f614497565b60405163d38fefd760e01b8152600660048201526001600160801b03909116602482015290828260448173dd0694ed7ecb6494a9055a78cf848a6b146c88b55af490811561097b5790611ad45761056d9150614c5e565b503461028057608036600319011261028057611b9061453c565b611b98614568565b611ba06144c3565b90606435611bac6152b3565b6001600160a01b0380611bdc856001600160801b03165f525f80516020615cdd83398151915260205260405f2090565b54163303611ca857859073652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f611c04846154b1565b96813b156105755760a48391859360405195869485936384c2dd4560e01b85526001600486015216998a602485015216998a60448401526001600160801b038091169b8c60648501521660848301525af48015610f8557611c94575b505060207ff9676dae71dbe3bd31576f3f9e20edfc7363381934d96b274adba466b956f79d91604051908152a46001815580f35b611c9d90614361565b610f8157845f611c60565b6040516348f5c3ed60e01b8152600490fd5b503461028057806003193601126102805761043f604051611cda816143c7565b601981527f5765696768746564496e746572657374526174654d6f64656c000000000000006020820152604051918291602083526020830190614299565b503461028057806003193601126102805760206040516001600160a01b037f00000000000000000000000000000000000000447e69651d841bd8d104bed493168152f35b503461028057806003193601126102805761043f604051611d7c816143c7565b601d81527f536574436f6c6c656374696f6e436f6c6c61746572616c46696c7465720000006020820152604051918291602083526020830190614299565b5034610280578060031936011261028057602063ffffffff60015460a01c16604051908152f35b503461028057606036600319011261028057611dfb614497565b611e6e602435611e096152b3565b6020611e24611e1f611e19614cde565b84614f37565b6154b1565b611e2f6044356154b1565b60405163284a271360e01b8152600160048201526001600160801b03808816602483015292831660448201529116606482015292839081906084820190565b038173652c78d85cdf25e85fe7df8d4c61b2a0f3c25d4f5af4918215610a9c578492611f13575b50926001916020947fd3d9500b000f4b80b8f9aea0e7ce7d135560d7775a63356999a9f3efd4f5edf86001600160801b0380841696611ed5888233615be0565b611eed846001600160a01b038954163090339061546f565b604080519485526001600160801b0395909516602085015216923392a355604051908152f35b9091506020813d8211611f4e575b81611f2e602093836143e2565b810103126105755792600191611f4560209561492c565b92509093611e95565b3d9150611f21565b503461028057806003193601126102805760206040516001600160a01b037f00000000000000000000000023b915eb10cafb2c5194e10d68932d7c6cc9aff3168152f35b503461028057806003193601126102805761043f604051611fba816143c7565b60038152620322e360ec1b6020820152604051918291602083526020830190614299565b503461028057604036600319011261028057611ff8614497565b906120016144ad565b60405163b481e93b60e01b8152600660048201526001600160801b03938416602482015292166044830152808260648173dd0694ed7ecb6494a9055a78cf848a6b146c88b55af49182156108095781926120a4575b509060405190602080830190808452825180925280604085019301945b8281106120805784840385f35b90919282610100826120956001948a516144d9565b01960191019492919094612073565b9091503d8083833e6120b681836143e2565b8101906020908181840312610575578051906001600160401b038211610f8157019082601f830112156105755781516120ee816145c4565b936120fc60405195866143e2565b818552828086019260081b85010193818511610e5e578301915b848310612129575050505050905f612056565b83610100916121388486614940565b815201920191612116565b503461028057806003193601126102805761043f604051612163816143c7565b6003815262322e3960e81b6020820152604051918291602083526020830190614299565b50346102805760e0366003190112610280576121a161457e565b6121a9614552565b9060a4356001600160401b038111610575576121c9903690600401614594565b610180526101405260c4356001600160401b038111610575576121f09036906004016142be565b93906121fa6152b3565b6122056114bc614cde565b610160526122138582614e6a565b959061221f8284614eca565b505061224461223b6122318486614dcf565b939099369161441e565b60643588614fda565b959298919790935f5b895181101561227d5761226a612263828c614654565b518c615930565b156115385761227890614639565b61224d565b5088908b612289614734565b966001600160401b0361229b89614647565b51166001600160401b03861611611a4e578751805f19810111611982575f999799190197885b612a0f575b50906122ee92916001600160a01b036009541694600154956001600160a01b0387169061595a565b926122fb610180516145c4565b986123096040519a8b6143e2565b610180518a52601f1961231e610180516145c4565b015f5b8181106129f15750505f8080610120525b610180516101205190811080806129e4575b156124c557156117635760051b6101405101356001600160801b0381160361175f5786612370916155bf565b5060c092835261238c896101205160051b6101405101356155bf565b5092918060809692965281511161172a57516080511492836124a2575b50505061172a57881061172a578a61246c916001600160801b036101205160051b610140510135165f528b61245d6001600160801b03602092600684528160016123fd8761169a60405f2095608051614f37565b920154168082101561249b57505b6124188561016051614f2a565b8082101561249457505b1693846040519361243285614390565b6001600160801b036101205160051b6101405101351685528401525f60408401526101205190614654565b526116fb8d6101205190614654565b976101205160051b6101405101359661248761012051614639565b6101205296989098612332565b9050612422565b905061240b565b8411801593509091906124ba575b50508d80806123a9565b111590508d806124b0565b50505086858c958b9361016051116119ea57602061012051116119ea5761ffff6101205111612990576124f6614852565b9061250761ffff6101205116614fa8565b935f9687968a89985b61ffff61012051168a10156125d8576001600160801b0390612536826118e18d86614654565b509391505060206125578d838361254d838a614654565b5101511696614654565b51015116916001600160401b03612571818c16928b614654565b51160292836001600160401b03811603611982576125a96119486125b5926119426125ca966001600160401b036125d0991690615631565b809d61016051916157b2565b6125bf8c8c614654565b5261196b8b8b614654565b98614639565b978b90612510565b92889196508a88938d61260f61271061260763ffffffff6125fc6101605188614f2a565b9c60a01c168c614f37565b04809a614f2a565b9788965f995b61ffff61012051168b1015612682576126768b61267c928f6126448e8e61263d8f8790614654565b51906157b2565b92604061189e6001600160801b03926126708460206126638489614654565b5101511661188b896154b1565b94614654565b9a614639565b99612615565b9186916127418e8e6001600160401b0397968061295b575b506127206126b16126a9614cde565b608435614f37565b926126bc878c614e6a565b9390946040519b8c98635174fe2d60e11b8a52600160048b01526101605160248b01521660448901526001600160a01b038b16606489015260643560848901528d60a489015260c488015260e4870152610160610104870152610164860190615251565b6101205161ffff166101248601528481036003190161014486015291614c13565b03908285732184298d600b5f2ff2febca0993626581a9940959381855af49586156106c85783958497612931575b50823b156105755761283c92849260405180958194829363a46c190760e01b84527ff0e5094ebd597f2042580340ce53d1b15e5b64e0d8be717ecde51dd37c61930060048501526001600160a01b038b16602485015260643560448501526001600160a01b037f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b1660648501526001600160a01b037f00000000000000000000000000000000000000447e69651d841bd8d104bed49316608485015260c060a485015260c4840191614c13565b03915af48015610f855761291d575b50506001600160a01b0381163b15610f81576040516323b872dd60e01b8152336004820152306024820152606480356044830152909186918391829084906001600160a01b03165af18015610e5357612909575b60208560016128ff87877f75cb20cd5ea276edc48a5c9c85e53729cc90bdd6776029b284aa2eb60a334ce26128f7896128e5600435336001600160a01b038a5416615307565b6040519182918a83528a830190614299565b0390a2614cac565b9155604051908152f35b6129138591614361565b610575578461289f565b61292690614361565b610f8157848661284b565b9096506129519195503d8085833e61294981836143e2565b810190615221565b949094958961276f565b612964906154b1565b604061296f84614647565b5101906129876001600160801b039182845116615a3c565b1690528b61269a565b60405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608490fd5b5061016051841415612344565b808c60208093612a029d9b9d61490e565b9201015201989698612321565b976001600160401b03612a25828b9c9a9c614654565b51166001600160401b0387161115612a4a578015611982575f190197889997996122c1565b979896986122c6565b50346102805760c0366003190112610280576004356001600160401b0381116106bd57612a849036906004016142be565b604435906001600160401b038216820361175f576084356001600160401b038111610f8157612ab7903690600401614594565b6101c05260a05260a435846001600160401b03821161028057612ae1612b2d9236906004016142be565b959093612aec6152b3565b612aff612af7614cde565b602435614f37565b6101e05260405193849283926394356ba560e01b845260016004850152604060248501526044840191614c13565b0381732184298d600b5f2ff2febca0993626581a9940955af48015610e535785869287926131f8575b50612b6090614cac565b93612b916001600160a01b0360e08501511694612b896101008601519861014087015192614dcf565b989096614fda565b9891925095612b9e614734565b966001600160401b03612bb089614647565b51166001600160401b03861611611a4e578751805f19810111611982575f999799190197885b6131b4575b5090612c0392916001600160a01b036009541694600154956001600160a01b0387169061595a565b92612c106101c0516145c4565b98612c1e6040519a8b6143e2565b6101c0518a52601f19612c336101c0516145c4565b015f5b8181106131965750505f80806101a0525b6101c0516101a0519081108080613189575b15612dd957156117635760051b60a05101356001600160801b0381160361175f5786612c84916155bf565b509161010052612c9f886101a05160051b60a05101356155bf565b50918060e095929552610100511161172a576101005160e051149283612db6575b50505061172a57881061172a578a612d81916001600160801b036101a05160051b60a0510135165f528b612d726001600160801b0360209260068452816001612d138761169a60405f209560e051614f37565b9201541680821015612daf57505b612d2e856101e051614f2a565b80821015612da857505b16938460405193612d4885614390565b6001600160801b036101a05160051b60a05101351685528401525f60408401526101a05190614654565b526116fb8d6101a05190614654565b976101a05160051b60a051013596612d9b6101a051614639565b6101a05296989098612c47565b9050612d38565b9050612d21565b841180159350909190612dce575b50505f8080612cc0565b111590505f80612dc4565b50505086858c958b936101e051116119ea5760206101a051116119ea5761ffff6101a0511161299057612e0a614852565b90612e1b61ffff6101a05116614fa8565b935f9687968a89985b61ffff6101a051168a1015612ec7576001600160801b0390612e4a826118e18d86614654565b50939150506020612e618d838361254d838a614654565b51015116916001600160401b03612e7b818c16928b614654565b51160292836001600160401b0381160361198257612eb36119486125b5926119426125ca966001600160401b03612ebf991690615631565b809d6101e051916157b2565b978b90612e24565b92889196508a88938d612eeb61271061260763ffffffff6125fc6101e05188614f2a565b9788965f995b61ffff6101a051168b1015612f1f576126768b612f19928f6126448e8e61263d8f8790614654565b99612ef1565b6001600160401b038d949392612fe28e8a9480613154575b50612fc16001600160a01b0360e08b015116986101008b015192612f64612f5c614cde565b606435614f37565b6101408d0151946040519c8d998a99635174fe2d60e11b8b52600160048c01526101e05160248c01521660448a0152606489015260848801528d60a488015260c487015260e4860152610160610104860152610164850190615251565b6101a05161ffff166101248501528381036003190161014485015290614299565b0381732184298d600b5f2ff2febca0993626581a9940955af49384156131495792869592600195926128ff956020999389956130db575b50917f1d7f9b884cc43b9a5c67bd7c6f6fb5ddb8cb3eb3994e934e8448d7bf4a4a25c98a836128f7957f75cb20cd5ea276edc48a5c9c85e53729cc90bdd6776029b284aa2eb60a334ce29795602435105f146130b35761309a906001600160a01b036080818f5416920151169061309260243585614f2a565b91309161546f565b604051908152a26040519182918a83528a830190614299565b506130d66001600160a01b038c54166130ce83602435614f2a565b903390615307565b61309a565b6128f7939195507f75cb20cd5ea276edc48a5c9c85e53729cc90bdd6776029b284aa2eb60a334ce294508a61313b7f1d7f9b884cc43b9a5c67bd7c6f6fb5ddb8cb3eb3994e934e8448d7bf4a4a25c9928c3d8091833e61294981836143e2565b979096509294506130199050565b6040513d88823e3d90fd5b61315d906154b1565b60406131688a614647565b5101906131806001600160801b039182845116615a3c565b1690528b612f37565b506101e051841415612c59565b808c602080936131a79d9b9d61490e565b9201015201989698612c36565b976001600160401b036131ca828b9c9a9c614654565b51166001600160401b03871611156131ef578015611982575f19019788999799612bd6565b97989698612bdb565b9050612b60925061321391503d8088833e610e7e81836143e2565b9290929190612b56565b50346102805760403660031901126102805760409061323a61453c565b826001600160a01b0361324b6144ad565b9216928381526007602052818120926001600160801b0380911693845f52602052825f205416938152600760205220905f52602052815f205460801c82519182526020820152f35b503461028057806003193601126102805760206001600160a01b0360015416604051908152f35b503461028057806003193601126102805761043f6040516132da816143c7565b60018152602036818301376001600160a01b03600954166132fa82614647565b526040519182918261431e565b503461028057602036600319011261028057613321614497565b90600254905f19908183019283116133b3576003549182019182116133b3576133498461553b565b9492959091861561172a571161172a571161172a57600282101561339f57506001149081613393575b5061172a57613382602091615a57565b6001600160a01b0360405191168152f35b6127109150115f613372565b634e487b7160e01b81526021600452602490fd5b634e487b7160e01b81526011600452602490fd5b5034610280576020366003190112610280576133e1614497565b6133e96149dd565b506001600160801b03604051916337fe8b4360e11b83526006600484015216602482015261010091828260448173dd0694ed7ecb6494a9055a78cf848a6b146c88b55af491821561080957819261344a575b5050610b6b60405180926144d9565b90928092503d831161347f575b61346181836143e2565b810190828183031261057557613478929350614940565b5f8061343b565b503d613457565b503461028057806003193601126102805761043f6134a2614734565b60405191829182614454565b5034610280578060031936011261028057604051806003805490818452602080940191815f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5f925b8281850110613598575092849261043f96926134a29554918482821061357f575b828210613563575b828210613547575b501061353a575b50905003826143e2565b60c01c8152018086613530565b600191946001600160401b038560801c16815201930184613529565b600191946001600160401b038560401c16815201930184613521565b600191946001600160401b038516815201930184613519565b9290936004600184928754906001600160401b03918281168252828160401c168c83015260809281841c16604083015260c01c606082015201960192019390506134f8565b5034610280576020908160031936011261028057600435916001600160401b038084116105795736602385011215610579576136246024943690868160040135910161441e565b6001600160a01b039160019280845416613a445782805181018681019060c0958691031261048b57613657878301614a3c565b9060408301518581116139e957818961367292860101614f4a565b9061367f60608501614a3c565b9461368c60808601614a3c565b9160a0860151888111613a4057818c6136a792890101615833565b958981015190898211613a3c57918c6136c39285940101615833565b961692805115613a2a57816001600160a01b0319951685600954161760095589808d905b6139ff575b5050507f5cc3a0ef4fb602d81e01a142e768b704108e3b2e96852939d75763e011a39b0091168382541617905560405163313ce56760e01b81528881600481855afa9081156139f4578a916139b6575b5060ff60129116116138665781875416178655339060045416176004556008815111613866578690855b6138bd575b50506008815111613866578590845b6137b1575b868052600686526040872060020180546001600160801b03166fffffffffffffffffffffffffffffffff191790558680f35b80518210156138b85781151580613878575b61386657826137d28383614654565b5116916003908154600160401b8110156138535787810180845581101561384057879461383a935f528160021c7fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0191878984549260061b1692831b921b1916179055614639565b9161377a565b8a634e487b7160e01b5f5260326004525ffd5b8a634e487b7160e01b5f5260416004525ffd5b604051630e52390960e41b8152600490fd5b50826138848383614654565b51165f1983018381116138a55761389c859184614654565b511610156137c3565b634e487b7160e01b895260116004528989fd5b61377f565b80518210156139b15781151580613971575b61386657836138de8383614654565b5116916002908154600160401b81101561395e5788810180845581101561394b5788948184613945955f521c7f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0191888a84549260061b1692831b921b1916179055614639565b91613766565b8b634e487b7160e01b5f5260326004525ffd5b8b634e487b7160e01b5f5260416004525ffd5b508361397d8383614654565b51165f19830183811161399e57613995869184614654565b511611156138cf565b634e487b7160e01b8a5260116004528a8afd5b61376b565b90508881813d83116139ed575b6139cd81836143e2565b810103126139e95760ff6139e2601292614a2e565b915061373c565b8980fd5b503d6139c3565b6040513d8c823e3d90fd5b8251811015613a255780611423613a19613a1f9386614654565b51615899565b816136e7565b6136ec565b60405163034b421560e01b8152600490fd5b8d80fd5b8c80fd5b60405162461bcd60e51b8152600481018690526013818901527f416c726561647920696e697469616c697a6564000000000000000000000000006044820152606490fd5b5034610280578060031936011261028057602061056d600554614c5e565b503461028057806003193601126102805760206001600160a01b037f5cc3a0ef4fb602d81e01a142e768b704108e3b2e96852939d75763e011a39b005416604051908152f35b503461028057806003193601126102805761043f604051613b0c816143c7565b60038152620312e360ec1b6020820152604051918291602083526020830190614299565b503461028057806003193601126102805760206040516001600160a01b037f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b168152f35b5034610280578060031936011261028057604051608081018181106001600160401b03821117613c5857604052600381526060366020830137613bb681614647565b906001600160a01b0391827f0000000000000000000000004512b49d3081e1d258eebef7c435f2310e7d309016905280516001101561176357817f00000000000000000000000000000000000000000000000000000000000000001660408201528051600210156117635761043f917f00000000000000000000000000000000000000000000000000000000000000001660608201526040519182918261431e565b634e487b7160e01b5f52604160045260245ffd5b50346102805760031990602036830112610280576001600160401b039160043583811161057957613ca19036906004016142be565b91613caa6152b3565b732184298d600b5f2ff2febca0993626581a99409594604051936341c9b3fd60e01b85526001600486015260406024860152858580613ced604482018589614c13565b03818a5af49485156131495786928796613f40575b505060e08201966001600160a01b0391828951169861010085019283519a813b15613f1b576040516334e9600560e01b81527ff0e5094ebd597f2042580340ce53d1b15e5b64e0d8be717ecde51dd37c61930060048201526001600160a01b039182166024820152604481019c909c527f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b811660648d01527f00000000000000000000000000000000000000447e69651d841bd8d104bed4931660848c015289908b9060a49082905af48015613f3557613f1f575b8899508381999899511693807f000000000000000000000000e0194f47040e2424b8a65cb5f7112a5dbe1f93bf1694845190803b15613f1b5760405163095ea7b360e01b81526001600160a01b038816600482015260248101929092528a908290604490829084905af19081156139f4578a91613f07575b5050806101409160015416925116935195015196843b156114315788968793613eb892613ea96040519b8c9a8b998a98630a61afeb60e01b8a5260048a01526024890152604488015260a0606488015260a4870190614299565b92858403016084860152614c13565b03925af18015610f8557613ef3575b50807f3e24a18afdf4b207766359f50e1aed359e8324d352510d7c8c86402b23101a5191a26001815580f35b613efc90614361565b6106bd57815f613ec7565b613f1090614361565b61143157885f613e4f565b8a80fd5b96979098613f2c90614361565b96958890613dd7565b6040513d8b823e3d90fd5b91955091503d8087843e613f5481846143e2565b820190604083830312610e5e578251908111610e5e57602091613f78918401614a95565b910151935f80613d02565b5034610280578060031936011261028057604051809182600a54808452602080940190600a84527fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8935b85828210613ff857505050613fe4925003836143e2565b61043f6040519282849384528301906142eb565b8554845260019586019588955093019201613fcd565b5034610280576040366003190112610280576004356001600160401b0381116106bd5761403f9036906004016142be565b906024359061404c6152b3565b6001600160a01b0392837f000000000000000000000000e0194f47040e2424b8a65cb5f7112a5dbe1f93bf163303611ca8576140be91859161409561408f614cde565b86614f37565b6040516256e44360e41b8152600160048201526060602482015294859384936064850191614c13565b9060448301520381732184298d600b5f2ff2febca0993626581a9940955af4928315610a9c578485928695614158575b50917f745f5ca6db42e779578fe4ae71bafbe1513d6daf820184b16204080eebedd34c939161411e604094614c5e565b80928161413b575b50505082519182526020820152a26001815580f35b82608061415094600154169201511690615307565b5f8181614126565b61411e9550604093507f745f5ca6db42e779578fe4ae71bafbe1513d6daf820184b16204080eebedd34c94929150614199903d8089833e610e7e81836143e2565b9650929492935090916140ee565b503461028057806003193601126102805760206040516101f48152f35b50346102805760203660031901126102805760043563ffffffff60e01b81168091036106bd57602090630956170560e11b811490811561420a575b506040519015158152f35b6301ffc9a760e01b149050826141ff565b9050346106bd57816003193601126106bd578061423a61043f926143c7565b601d81527f576569676874656452617465536574436f6c6c656374696f6e506f6f6c0000006020820152604051918291602083526020830190614299565b5f5b8381106142895750505f910152565b818101518382015260200161427a565b906020916142b281518092818552858086019101614278565b601f01601f1916010190565b9181601f8401121561175f578235916001600160401b03831161175f576020838186019501011161175f57565b9081518082526020808093019301915f5b82811061430a575050505090565b8351855293810193928101926001016142fc565b602090816040818301928281528551809452019301915f5b828110614344575050505090565b83516001600160a01b031685529381019392810192600101614336565b6001600160401b038111613c5857604052565b61018081019081106001600160401b03821117613c5857604052565b606081019081106001600160401b03821117613c5857604052565b61010081019081106001600160401b03821117613c5857604052565b604081019081106001600160401b03821117613c5857604052565b90601f801991011681019081106001600160401b03821117613c5857604052565b6001600160401b038111613c5857601f01601f191660200190565b92919261442a82614403565b9161443860405193846143e2565b82948184528183011161175f578281602093845f960137010152565b602090816040818301928281528551809452019301915f5b82811061447a575050505090565b83516001600160401b03168552938101939281019260010161446c565b600435906001600160801b038216820361175f57565b602435906001600160801b038216820361175f57565b604435906001600160801b038216820361175f57565b60e090816001600160801b03918281511685528260208201511660208601528260408201511660408601528260608201511660608601528260808201511660808601528260a08201511660a08601528260c08201511660c0860152015116910152565b600435906001600160a01b038216820361175f57565b604435906001600160a01b038216820361175f57565b602435906001600160a01b038216820361175f57565b602435906001600160401b038216820361175f57565b9181601f8401121561175f578235916001600160401b03831161175f576020808501948460051b01011161175f57565b6001600160401b038111613c585760051b60200190565b81601f8201121561175f578035916145f2836145c4565b9261460060405194856143e2565b808452602092838086019260051b82010192831161175f578301905b82821061462a575050505090565b8135815290830190830161461c565b5f1981146119825760010190565b8051156117635760200190565b80518210156117635760209160051b010190565b3d15614692573d9061467982614403565b9161468760405193846143e2565b82523d5f602084013e565b606090565b919290156146f957508151156146ab575090565b3b156146b45790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b82519091501561470c5750805190602001fd5b60405162461bcd60e51b815260206004820152908190614730906024830190614299565b0390fd5b60409081519182600254918282526020908183019060025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace945f5b81600382011061480e57846147ac9754938383106147f3575b8383106147d7575b508282106147bb575b50106147ae575b50905003836143e2565b565b60c01c815201805f6147a2565b600191946001600160401b038560801c1681520193018461479b565b946001600160401b0385600194971c168152019301845f614792565b9194816001916001600160401b03871681520195019161478a565b86546001600160401b03808216865281851c811686880152608082811c9091168686015260c09190911c606086015260019097019689965090930192600401614771565b60409081519182600391825480835260209182840191855f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b955f905b82818301106148c75750846147ac9754938383106147f3578383106147d757508282106147bb5750106147ae5750905003836143e2565b87546001600160401b03808216875281861c811687890152608082811c9091168787015260c09190911c60608701526001909801978a975090940193600490910190614890565b6040519061491b82614390565b5f6040838281528260208201520152565b51906001600160801b038216820361175f57565b91908261010091031261175f57604051614959816143ab565b60e06149d881839561496a8161492c565b85526149786020820161492c565b60208601526149896040820161492c565b604086015261499a6060820161492c565b60608601526149ab6080820161492c565b60808601526149bc60a0820161492c565b60a08601526149cd60c0820161492c565b60c08601520161492c565b910152565b604051906149ea826143ab565b8160e05f918281528260208201528260408201528260608201528260808201528260a08201528260c08201520152565b51906001600160401b038216820361175f57565b519060ff8216820361175f57565b51906001600160a01b038216820361175f57565b81601f8201121561175f578051614a6681614403565b92614a7460405194856143e2565b8184526020828401011161175f57614a929160208085019101614278565b90565b91906101808382031261175f576040928351614ab081614374565b8094614abb83614a2e565b8252602090818401518284015280840151818401526060918285015183850152614ae760808601614a3c565b6080850152614af860a08601614a1a565b60a0850152614b0960c08601614a1a565b60c0850152614b1a60e08601614a3c565b60e08501526101008086015190850152610120808601519061ffff8216820361175f5785015261014080860151906001600160401b039182811161175f5788614b64918901614a50565b90860152610160958681015191821161175f57019186601f8401121561175f57825190614b90826145c4565b97614b9d8251998a6143e2565b8289528386818b0194028601019481861161175f578401925b858410614bc857505050505050500152565b868483031261175f578487918451614bdf81614390565b614be88761492c565b8152614bf583880161492c565b83820152614c0486880161492c565b86820152815201930192614bb6565b908060209392818452848401375f828201840152601f01601f1916010190565b8115614c3d570490565b634e487b7160e01b5f52601260045260245ffd5b9190820180921161198257565b614c66614cde565b8015614c3d5780820615918215614ca3575b5f9215614c8957614a929250614c33565b90614c9391614c33565b90600182018092116133b3575090565b60019250614c78565b614cb4614cde565b8015614c3d5780820615918215614cd6575f9215614c8957614a929250614c33565b5f9250614c78565b600460206001600160a01b03600154166040519283809263313ce56760e01b82525afa8015614d68575f90614d2f575b60ff91501660120360ff81116119825760ff16604d811161198257600a0a90565b6020823d8211614d60575b81614d47602093836143e2565b810103126102805750614d5b60ff91614a2e565b614d0e565b3d9150614d3a565b6040513d5f823e3d90fd5b9093929384831161175f57841161175f578101920390565b7fffff0000000000000000000000000000000000000000000000000000000000009035818116939260028110614dc057505050565b60020360031b82901b16169150565b905f5b818110614de0575050905f90565b60028101908181116119825760049081810191828411614e575750614e21614e1383614e19614e13876005968a8c614d73565b90614d8b565b958789614d73565b9260f093841c931c14614e3d5790614e3891614c51565b614dd2565b9091614e4d614e53949383614c51565b92614d73565b9091565b601190634e487b7160e01b5f525260245ffd5b905f5b818110614e7b575050905f90565b60028101908181116119825760049081810191828411614e575750614eae614e1383614e19614e13876001968a8c614d73565b9260f093841c931c14614e3d5790614ec591614c51565b614e6d565b905f5b818110614edb575050905f90565b600290818101918282116119825760049182810192838511614e575750614e1383614e19614e1387614f0e958a8c614d73565b9260f093841c931c14614e3d5790614f2591614c51565b614ecd565b9190820391821161198257565b8181029291811591840414171561198257565b81601f8201121561175f57805191614f61836145c4565b92614f6f60405194856143e2565b808452602092838086019260051b82010192831161175f578301905b828210614f99575050505090565b81518152908301908301614f8b565b90614fb2826145c4565b614fbf60405191826143e2565b8281528092614fd0601f19916145c4565b0190602036910137565b90916001600160a01b0380831690807f0000000000000000000000004512b49d3081e1d258eebef7c435f2310e7d30901682149081156151f5575b81156151c9575b5061506f5750509160405191615031836143c7565b600183526020368185013761504583614647565b5260405190615053826143c7565b6001825260203681840137600161506983614647565b52600190565b92915092604091825194632ab54d1560e01b86528160048701528360248701525f908187806150a16044820185614299565b0381895afa9586156151bf57829383988498615144575b50916020916150e994938851809681948293631ad854b160e31b845260048401528b60248401526044830190614299565b03915afa94851561513a57508094615104575b505093929190565b909193506020823d8211615132575b81615120602093836143e2565b81010312610280575051915f806150fc565b3d9150615113565b51903d90823e3d90fd5b9450965096503d8083853e61515981856143e2565b8301966060848903126105795761516f84614a3c565b9760208501516001600160401b03908181116112975782615191918801614f4a565b9588810151918211611297576150e9959492602094926151b19201614f4a565b9995999891939450916150b8565b85513d84823e3d90fd5b90507f00000000000000000000000000000000000000000000000000000000000000001681145f61501c565b7f0000000000000000000000000000000000000000000000000000000000000000811683149150615015565b919060408382031261175f5782516001600160401b03811161175f5760209161524b918501614a50565b92015190565b9081518082526020808093019301915f5b828110615270575050505090565b90919293826060826152a76001948951604090816001600160801b0391828151168552826020820151166020860152015116910152565b01950193929101615262565b60025f54146152c25760025f55565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526147ac9161534a82606481015b03601f1981018452836143e2565b6001600160a01b0316906153a9604051615363816143c7565b6020938482527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858301525f808587829751910182855af16153a3614668565b91614697565b80519182159184831561541b575b5050509050156153c45750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b9193818094500103126106bd578201519081151582036102805750805f80846153b7565b909160608284031261175f5781519260208301516001600160401b03811161175f5760409161524b918501614a95565b6040516323b872dd60e01b60208201526001600160a01b03928316602482015292909116604483015260648201929092526147ac9161534a826084810161533c565b6001600160801b03908181116154c5571690565b60405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608490fd5b919082604091031261175f57614a9260206155348461492c565b930161492c565b6effffffffffffffffffffffffffffff8160081c169060078160051c169160078260021c16915f906001600160801b03808216145f146155ab5750505f905b8160028110156155975760010361559457615594906156e9565b93565b634e487b7160e01b5f52602160045260245ffd5b60031690600282101561339f57509061557a565b6effffffffffffffffffffffffffffff8160081c169160078260051c169260078360021c16925f906001600160801b03808216145f1461561d5750505f915b8260028110156155975760010361561957906155949161574b565b5093565b60031690600282101561339f5750916155fe565b905f19818309818302918280831092039180830392146156d857670de0b6b3a76400009082821115615693577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac10669940990828211900360ee1b910360121c170290565b60405162461bcd60e51b815260206004820152601560248201527f4d6174683a206d756c446976206f766572666c6f7700000000000000000000006044820152606490fd5b5050670de0b6b3a764000091500490565b6127105f19828209828202918280831092039180830392146157445781811115615693577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e9193810990828211900360fc1b910360041c170290565b9250500490565b905f19818309818302918280831092039180830392146157a7576127109082821115615693577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b505061271091500490565b915f19828409928281029283808610950394808603951461582657848311156156935782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b505090614a929250614c33565b81601f8201121561175f5780519161584a836145c4565b9261585860405194856143e2565b808452602092838086019260051b82010192831161175f578301905b828210615882575050505090565b83809161588e84614a1a565b815201910190615874565b5f818152600b602052604081205461592b57600a54600160401b811015615917576001810180600a558110156159035790826040927fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80155600a54928152600b6020522055600190565b634e487b7160e01b82526032600452602482fd5b634e487b7160e01b82526041600452602482fd5b905090565b6001600160a01b038060095416911603615955575f52600b60205260405f2054151590565b505f90565b949290926001600160a01b0394857f5cc3a0ef4fb602d81e01a142e768b704108e3b2e96852939d75763e011a39b005416935f8515155f14615a305750928697926159ed92613ea96159da60209a976040519c8d9b8c9a8b9a633a596a2b60e21b8c521660048b015216602489015260a0604489015260a48801906142eb565b60031994858883030160648901526142eb565b03915afa908115614d68575f91615a02575090565b906020823d8211615a28575b81615a1b602093836143e2565b8101031261028057505190565b3d9150615a0e565b97505050505050505090565b9190916001600160801b038080941691160191821161198257565b6001600160a01b0380615a87836001600160801b03165f525f80516020615cdd83398151915260205260405f2090565b541680615b8b57506001600160801b0360405192636afdaf8360e01b8452169081600484015260208360248173cc985298cc007901c9994920fecedadbeba8380f5af4928315614d68575f93615b50575b50815f525f80516020615cdd83398151915260205260405f209080841691826001600160a01b03198254161790557f00000000000000000000000023b915eb10cafb2c5194e10d68932d7c6cc9aff316907f8becc264fc02f46cc9b9aac9c208f0b4b4289153fbec5e87a5562c96d0dcb3845f80a490565b90926020823d8211615b83575b81615b6a602093836143e2565b810103126102805750615b7c90614a3c565b915f615ad8565b3d9150615b5d565b916001600160801b0316907f00000000000000000000000023b915eb10cafb2c5194e10d68932d7c6cc9aff316827f8becc264fc02f46cc9b9aac9c208f0b4b4289153fbec5e87a5562c96d0dcb3845f80a490565b6001600160801b035f921682525f80516020615cdd8339815191526020526001600160a01b0390816040842054168015615c5f57803b1561057557839291836064926040519788958694635d5b6e6560e01b865284600487015216602485015260448401525af190811561097b5750615c565750565b6147ac90614361565b5050505050565b6001600160801b035f921682525f80516020615cdd8339815191526020526001600160a01b0390816040842054168015615c5f57803b1561057557839291836064926040519788958694635d5b6e6560e01b865216600485015282602485015260448401525af190811561097b5750615c56575056fec61d9ab4916a5eab6b572dc8707662b99e55e17ecdc61af8ff79465ad64ded00a26469706673582212209c163dc377232bfbf29444ca8080a2266bf98ab2d603360c6699f9b03eb3bae864736f6c63430008140033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.