Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0xF50e40FF2A6A7267B9a33e71EE0C1b62F015aF9B
Balance 0 ETH
Nonce 1
Code Size 5105 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

5105 bytes
0x608060405234801561000f575f80fd5b506004361061013b575f3560e01c8062551c061461013f5780630490be831461018357806311449b611461019a5780631559f782146101a95780631be5c92f146101c9578063200d2ed2146101d15780632217455a146101f257806322cb9c6f146101fa578063243a6e6c146102225780632f60d0711461022b5780633078fff51461023557806334a93f251461024d578063452a93201461026057806348c9d067146102685780635760f2e3146102855780636907678814610298578063791b98bc146102bf5780637c9e639f146102d2578063931cc314146102e15780639ee4c057146102f4578063b566aa1a14610307578063cf0b00c61461031a578063d541d9e61461032d578063f2041b3814610340578063f56f48f214610348578063ff29dd9614610351575b5f80fd5b6101667f000000000000000000000000ad29b3738ea37f1eb8a8f6745aeef51399fce57b81565b6040516001600160a01b0390911681526020015b60405180910390f35b61018c60035481565b60405190815260200161017a565b61018c6702c68af0bb14000081565b6101bc6101b736600461102d565b610364565b60405161017a9190611074565b61018c601281565b6002546101e590600160a81b900460ff1681565b60405161017a91906110d7565b61018c6103b2565b61020d6102083660046110fe565b610409565b6040805192835260208301919091520161017a565b61018c60015481565b610233610485565b005b6002546101669061010090046001600160a01b031681565b61023361025b366004611141565b610498565b610166610516565b6002546102759060ff1681565b604051901515815260200161017a565b610233610293366004611141565b610524565b6101667f0000000000000000000000001e7460c8527282fd15b4bc7bbc451cd20d95b14e81565b600454610166906001600160a01b031681565b61018c670a688906bd8b000081565b6102336102ef36600461102d565b6105b3565b61023361030236600461102d565b610648565b61018c610315366004611167565b610759565b61020d6103283660046110fe565b610796565b61018c61033b366004611167565b6107c4565b61018c61085d565b61018c61384081565b61023361035f366004611198565b61090a565b61036c610fb3565b5f6103756103b2565b905061037f610fb3565b606081018290526080810182905260a081018290528181526020810182905260408101919091525f60c082015292915050565b5f806103bc610960565b90506103c781610b3d565b156103d457505060035490565b6103dd81610ba3565b156103ea57505060035490565b6103ff8160200151826080015160ff16610be7565b91505090565b5090565b5f80336001600160a01b037f000000000000000000000000ad29b3738ea37f1eb8a8f6745aeef51399fce57b16146104715760405162461bcd60e51b81526020600482015260066024820152654f6e6c79504360d01b60448201526064015b60405180910390fd5b61047961085d565b965f9650945050505050565b61048d610c49565b6104965f610cb6565b565b6104a0610c49565b6001600160a01b03811661050a5760405162461bcd60e51b815260206004820152602b60248201527f477561726461626c653a206e657720677561726469616e20697320746865207a60448201526a65726f206164647265737360a81b6064820152608401610468565b61051381610cb6565b50565b5f546001600160a01b031690565b61052c610c49565b6004546001600160a01b0316156105915760405162461bcd60e51b8152602060048201526024808201527f706f736974696f6e4d616e616765722068617320616c7265616479206265656e604482015263081cd95d60e21b6064820152608401610468565b600480546001600160a01b0319166001600160a01b0392909216919091179055565b6105bb610c49565b6001600254600160a81b900460ff1660018111156105db576105db611044565b146106365760405162461bcd60e51b815260206004820152602560248201527f507269636546656564206d7573742062652062726f6b656e20746f20696e74656044820152647276656e6560d81b6064820152608401610468565b61063f81610d05565b6105135f610d3a565b610650610c49565b6702c68af0bb1400008110156106b25760405162461bcd60e51b815260206004820152602160248201527f446576696174696f6e2062656c6f77206d696e696d756d207468726573686f6c6044820152601960fa1b6064820152608401610468565b670a688906bd8b00008111156107145760405162461bcd60e51b815260206004820152602160248201527f446576696174696f6e2061626f7665206d6178696d756d207468726573686f6c6044820152601960fa1b6064820152608401610468565b600180549082905560408051828152602081018490527fea7b0359048504e79474eaaa05294b49265fc4e7b0a0c3737aaa22412e90f16e910160405180910390a15050565b6004545f906001600160a01b031633146107855760405162461bcd60e51b8152600401610468906111b1565b61078d61085d565b90505b92915050565b6004545f9081906001600160a01b031633146104715760405162461bcd60e51b8152600401610468906111b1565b5f336001600160a01b037f000000000000000000000000ad29b3738ea37f1eb8a8f6745aeef51399fce57b1614806108245750336001600160a01b037f0000000000000000000000001e7460c8527282fd15b4bc7bbc451cd20d95b14e16145b6107855760405162461bcd60e51b815260206004820152600a6024820152694f6e6c7950436f72434360b01b6044820152606401610468565b5f80600254600160a81b900460ff16600181111561087d5761087d611044565b146108ca5760405162461bcd60e51b815260206004820152601f60248201527f436861696e6c696e6b204f7261636c6520436972637569742042726f6b656e006044820152606401610468565b5f6108d3610960565b90506108de81610b3d565b156108eb576103ff610d93565b6108f481610ba3565b15610901576103ff610d93565b6103ff81610da5565b610912610c49565b6002805460ff191682151590811790915560405160ff909116151581527fab9c7aa16f84e4e5d12316e868fc65e8610fd13cc34831d13edc42cc6991c7a5906020015b60405180910390a150565b610968611000565b5f5a9050600260019054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156109db575060408051601f3d908101601f191682019092526109d8918101906111d1565b60015b610a36576109ea604082611205565b5a111580156109fb575060025460ff165b15610405576040516317e6281f60e11b815260206004820152600a602482015269646563696d616c73282960b01b6044820152606401610468565b60ff1660808301525f5a9050600260019054906101000a90046001600160a01b03166001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa925050508015610ab1575060408051601f3d908101601f19168201909252610aae9181019061123a565b60015b610b1857610ac0604082611205565b5a11158015610ad1575060025460ff165b15610b13576040516317e6281f60e11b81526020600482015260116024820152706c6174657374526f756e6444617461282960781b6044820152606401610468565b505090565b506001600160501b039093168652506020850152604084015250506001606082015290565b5f8160600151610b4f57506001919050565b81516001600160501b03165f03610b6857506001919050565b60408201511580610b7c5750428260400151115b15610b8957506001919050565b5f826020015113610b9c57506001919050565b505f919050565b5f80610bb6835f01518460800151610dc7565b9050610bc181610b3d565b80610bd05750610bd083610ef0565b80610be05750610be08382610f0b565b9392505050565b5f8060128310610c1857610bfc601284611286565b610c0790600a611379565b610c119085611205565b905061078d565b601283101561078d57610c2c836012611286565b610c3790600a611379565b610c419085611384565b949350505050565b33610c52610516565b6001600160a01b0316146104965760405162461bcd60e51b815260206004820152602560248201527f477561726461626c653a2063616c6c6572206973206e6f742074686520677561604482015264393234b0b760d91b6064820152608401610468565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f0f4b7c1c615f05db5d396c69cf5146c8f2949c2b53ef79a3260b33fbed11682d9190a35050565b60038190556040518181527f4d29de21de555af78a62fc82dd4bc05e9ae5b0660a37f04729527e0f22780cd390602001610955565b6002805482919060ff60a81b1916600160a81b836001811115610d5f57610d5f611044565b02179055507f5c57579a8214fe4f710c1c56fa829f045b9fa6d225a744225a30c32188064d4e8160405161095591906110d7565b5f610d9e6001610d3a565b5060035490565b5f80610dbc8360200151846080015160ff16610be7565b905061079081610d05565b610dcf611000565b5f5a60025490915061010090046001600160a01b0316639a6fc8f5610df560018761139b565b6040516001600160e01b031960e084901b1681526001600160501b03909116600482015260240160a060405180830381865afa925050508015610e55575060408051601f3d908101601f19168201909252610e529181019061123a565b60015b610eba57610e64604082611205565b5a11158015610e75575060025460ff165b15610eb4576040516317e6281f60e11b815260206004820152600e60248201526d676574526f756e6444617461282960901b6044820152606401610468565b50610790565b506001600160501b03909316855250602084015260408301525060ff8216608082015260016060820152610790565b5092915050565b5f613840826040015142610f049190611286565b1192915050565b5f80610f228460200151856080015160ff16610be7565b90505f610f3a8460200151856080015160ff16610be7565b90505f610f478383610f8f565b90505f610f548484610fa4565b90505f81670de0b6b3a7640000610f6b8583611286565b610f759190611384565b610f7f9190611205565b6001541098975050505050505050565b5f818310610f9d578161078d565b5090919050565b5f81831015610f9d578161078d565b6040518061010001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f6001811115610ffb57610ffb611044565b905290565b6040805160a0810182525f8082526020820181905291810182905260608101829052608081019190915290565b5f6020828403121561103d575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b6002811061051357634e487b7160e01b5f52602160045260245ffd5b5f61010082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e08301516110ca81611058565b8060e08401525092915050565b602081016110e483611058565b91905290565b803580151581146110f9575f80fd5b919050565b5f805f8060808587031215611111575f80fd5b8435935060208501359250611128604086016110ea565b9150611136606086016110ea565b905092959194509250565b5f60208284031215611151575f80fd5b81356001600160a01b038116811461078d575f80fd5b5f8060408385031215611178575f80fd5b611181836110ea565b915061118f602084016110ea565b90509250929050565b5f602082840312156111a8575f80fd5b61078d826110ea565b6020808252600690820152654f6e6c79504d60d01b604082015260600190565b5f602082840312156111e1575f80fd5b815160ff8116811461078d575f80fd5b634e487b7160e01b5f52601160045260245ffd5b5f8261121f57634e487b7160e01b5f52601260045260245ffd5b500490565b80516001600160501b03811681146110f9575f80fd5b5f805f805f60a0868803121561124e575f80fd5b61125786611224565b945060208601519350604086015192506060860151915061127a60808701611224565b90509295509295909350565b81810381811115610790576107906111f1565b600181815b808511156112d357815f19048211156112b9576112b96111f1565b808516156112c657918102915b93841c939080029061129e565b509250929050565b5f826112e957506001610790565b816112f557505f610790565b816001811461130b576002811461131557611331565b6001915050610790565b60ff841115611326576113266111f1565b50506001821b610790565b5060208310610133831016604e8410600b8410161715611354575081810a610790565b61135e8383611299565b805f1904821115611371576113716111f1565b029392505050565b5f61078d83836112db565b8082028115828204841417610790576107906111f1565b6001600160501b03828116828216039080821115610ee957610ee96111f156fea2646970667358221220f4647f443610c3101a86b55e28880b8ff34d5eebbc37b3bb2aff5fd81628d5fc64736f6c63430008150033

Verified Source Code Full Match

Compiler: v0.8.21+commit.d9974bed EVM: shanghai Optimization: Yes (1 runs)
BaseMath.sol 6 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

contract BaseMath {
    uint constant public DECIMAL_PRECISION = 1e18;
}
AggregatorV3Interface.sol 32 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(uint80 _roundId)
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}
AccessControl.sol 248 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(account),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}
IAccessControl.sol 88 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
TimelockController.sol 422 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol)

pragma solidity ^0.8.0;

import "../access/AccessControl.sol";
import "../token/ERC721/IERC721Receiver.sol";
import "../token/ERC1155/IERC1155Receiver.sol";

/**
 * @dev Contract module which acts as a timelocked controller. When set as the
 * owner of an `Ownable` smart contract, it enforces a timelock on all
 * `onlyOwner` maintenance operations. This gives time for users of the
 * controlled contract to exit before a potentially dangerous maintenance
 * operation is applied.
 *
 * By default, this contract is self administered, meaning administration tasks
 * have to go through the timelock process. The proposer (resp executor) role
 * is in charge of proposing (resp executing) operations. A common use case is
 * to position this {TimelockController} as the owner of a smart contract, with
 * a multisig or a DAO as the sole proposer.
 *
 * _Available since v3.3._
 */
contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver {
    bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
    bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
    bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
    bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
    uint256 internal constant _DONE_TIMESTAMP = uint256(1);

    mapping(bytes32 => uint256) private _timestamps;
    uint256 private _minDelay;

    /**
     * @dev Emitted when a call is scheduled as part of operation `id`.
     */
    event CallScheduled(
        bytes32 indexed id,
        uint256 indexed index,
        address target,
        uint256 value,
        bytes data,
        bytes32 predecessor,
        uint256 delay
    );

    /**
     * @dev Emitted when a call is performed as part of operation `id`.
     */
    event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);

    /**
     * @dev Emitted when new proposal is scheduled with non-zero salt.
     */
    event CallSalt(bytes32 indexed id, bytes32 salt);

    /**
     * @dev Emitted when operation `id` is cancelled.
     */
    event Cancelled(bytes32 indexed id);

    /**
     * @dev Emitted when the minimum delay for future operations is modified.
     */
    event MinDelayChange(uint256 oldDuration, uint256 newDuration);

    /**
     * @dev Initializes the contract with the following parameters:
     *
     * - `minDelay`: initial minimum delay for operations
     * - `proposers`: accounts to be granted proposer and canceller roles
     * - `executors`: accounts to be granted executor role
     * - `admin`: optional account to be granted admin role; disable with zero address
     *
     * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
     * without being subject to delay, but this role should be subsequently renounced in favor of
     * administration through timelocked proposals. Previous versions of this contract would assign
     * this admin to the deployer automatically and should be renounced as well.
     */
    constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) {
        _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
        _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
        _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
        _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);

        // self administration
        _setupRole(TIMELOCK_ADMIN_ROLE, address(this));

        // optional admin
        if (admin != address(0)) {
            _setupRole(TIMELOCK_ADMIN_ROLE, admin);
        }

        // register proposers and cancellers
        for (uint256 i = 0; i < proposers.length; ++i) {
            _setupRole(PROPOSER_ROLE, proposers[i]);
            _setupRole(CANCELLER_ROLE, proposers[i]);
        }

        // register executors
        for (uint256 i = 0; i < executors.length; ++i) {
            _setupRole(EXECUTOR_ROLE, executors[i]);
        }

        _minDelay = minDelay;
        emit MinDelayChange(0, minDelay);
    }

    /**
     * @dev Modifier to make a function callable only by a certain role. In
     * addition to checking the sender's role, `address(0)` 's role is also
     * considered. Granting a role to `address(0)` is equivalent to enabling
     * this role for everyone.
     */
    modifier onlyRoleOrOpenRole(bytes32 role) {
        if (!hasRole(role, address(0))) {
            _checkRole(role, _msgSender());
        }
        _;
    }

    /**
     * @dev Contract might receive/hold ETH as part of the maintenance process.
     */
    receive() external payable {}

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControl) returns (bool) {
        return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns whether an id correspond to a registered operation. This
     * includes both Pending, Ready and Done operations.
     */
    function isOperation(bytes32 id) public view virtual returns (bool) {
        return getTimestamp(id) > 0;
    }

    /**
     * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
     */
    function isOperationPending(bytes32 id) public view virtual returns (bool) {
        return getTimestamp(id) > _DONE_TIMESTAMP;
    }

    /**
     * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
     */
    function isOperationReady(bytes32 id) public view virtual returns (bool) {
        uint256 timestamp = getTimestamp(id);
        return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
    }

    /**
     * @dev Returns whether an operation is done or not.
     */
    function isOperationDone(bytes32 id) public view virtual returns (bool) {
        return getTimestamp(id) == _DONE_TIMESTAMP;
    }

    /**
     * @dev Returns the timestamp at which an operation becomes ready (0 for
     * unset operations, 1 for done operations).
     */
    function getTimestamp(bytes32 id) public view virtual returns (uint256) {
        return _timestamps[id];
    }

    /**
     * @dev Returns the minimum delay for an operation to become valid.
     *
     * This value can be changed by executing an operation that calls `updateDelay`.
     */
    function getMinDelay() public view virtual returns (uint256) {
        return _minDelay;
    }

    /**
     * @dev Returns the identifier of an operation containing a single
     * transaction.
     */
    function hashOperation(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32) {
        return keccak256(abi.encode(target, value, data, predecessor, salt));
    }

    /**
     * @dev Returns the identifier of an operation containing a batch of
     * transactions.
     */
    function hashOperationBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32) {
        return keccak256(abi.encode(targets, values, payloads, predecessor, salt));
    }

    /**
     * @dev Schedule an operation containing a single transaction.
     *
     * Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function schedule(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _schedule(id, delay);
        emit CallScheduled(id, 0, target, value, data, predecessor, delay);
        if (salt != bytes32(0)) {
            emit CallSalt(id, salt);
        }
    }

    /**
     * @dev Schedule an operation containing a batch of transactions.
     *
     * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function scheduleBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == payloads.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
        _schedule(id, delay);
        for (uint256 i = 0; i < targets.length; ++i) {
            emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay);
        }
        if (salt != bytes32(0)) {
            emit CallSalt(id, salt);
        }
    }

    /**
     * @dev Schedule an operation that is to become valid after a given delay.
     */
    function _schedule(bytes32 id, uint256 delay) private {
        require(!isOperation(id), "TimelockController: operation already scheduled");
        require(delay >= getMinDelay(), "TimelockController: insufficient delay");
        _timestamps[id] = block.timestamp + delay;
    }

    /**
     * @dev Cancel an operation.
     *
     * Requirements:
     *
     * - the caller must have the 'canceller' role.
     */
    function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
        require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
        delete _timestamps[id];

        emit Cancelled(id);
    }

    /**
     * @dev Execute an (ready) operation containing a single transaction.
     *
     * Emits a {CallExecuted} event.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
    // thus any modifications to the operation during reentrancy should be caught.
    // slither-disable-next-line reentrancy-eth
    function execute(
        address target,
        uint256 value,
        bytes calldata payload,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        bytes32 id = hashOperation(target, value, payload, predecessor, salt);

        _beforeCall(id, predecessor);
        _execute(target, value, payload);
        emit CallExecuted(id, 0, target, value, payload);
        _afterCall(id);
    }

    /**
     * @dev Execute an (ready) operation containing a batch of transactions.
     *
     * Emits one {CallExecuted} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
    // thus any modifications to the operation during reentrancy should be caught.
    // slither-disable-next-line reentrancy-eth
    function executeBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata payloads,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == payloads.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);

        _beforeCall(id, predecessor);
        for (uint256 i = 0; i < targets.length; ++i) {
            address target = targets[i];
            uint256 value = values[i];
            bytes calldata payload = payloads[i];
            _execute(target, value, payload);
            emit CallExecuted(id, i, target, value, payload);
        }
        _afterCall(id);
    }

    /**
     * @dev Execute an operation's call.
     */
    function _execute(address target, uint256 value, bytes calldata data) internal virtual {
        (bool success, ) = target.call{value: value}(data);
        require(success, "TimelockController: underlying transaction reverted");
    }

    /**
     * @dev Checks before execution of an operation's calls.
     */
    function _beforeCall(bytes32 id, bytes32 predecessor) private view {
        require(isOperationReady(id), "TimelockController: operation is not ready");
        require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency");
    }

    /**
     * @dev Checks after execution of an operation's calls.
     */
    function _afterCall(bytes32 id) private {
        require(isOperationReady(id), "TimelockController: operation is not ready");
        _timestamps[id] = _DONE_TIMESTAMP;
    }

    /**
     * @dev Changes the minimum timelock duration for future operations.
     *
     * Emits a {MinDelayChange} event.
     *
     * Requirements:
     *
     * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
     * an operation where the timelock is the target and the data is the ABI-encoded call to this function.
     */
    function updateDelay(uint256 newDelay) external virtual {
        require(msg.sender == address(this), "TimelockController: caller must be timelock");
        emit MinDelayChange(_minDelay, newDelay);
        _minDelay = newDelay;
    }

    /**
     * @dev See {IERC721Receiver-onERC721Received}.
     */
    function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }

    /**
     * @dev See {IERC1155Receiver-onERC1155Received}.
     */
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    /**
     * @dev See {IERC1155Receiver-onERC1155BatchReceived}.
     */
    function onERC1155BatchReceived(
        address,
        address,
        uint256[] memory,
        uint256[] memory,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }
}
IERC2612.sol 8 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2612.sol)

pragma solidity ^0.8.0;

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

interface IERC2612 is IERC20Permit {}
IERC5267.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.0;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}
IERC1155Receiver.sol 58 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
ERC20.sol 365 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
IERC20.sol 78 lines
// 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);
}
ERC20Permit.sol 95 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.0;

import "./IERC20Permit.sol";
import "../ERC20.sol";
import "../../../utils/cryptography/ECDSA.sol";
import "../../../utils/cryptography/EIP712.sol";
import "../../../utils/Counters.sol";

/**
 * @dev Implementation 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.
 *
 * _Available since v3.4._
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
    using Counters for Counters.Counter;

    mapping(address => Counters.Counter) private _nonces;

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    /**
     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
     * However, to ensure consistency with the upgradeable transpiler, we will continue
     * to reserve a slot.
     * @custom:oz-renamed-from _PERMIT_TYPEHASH
     */
    // solhint-disable-next-line var-name-mixedcase
    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @inheritdoc IERC20Permit
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    function nonces(address owner) public view virtual override returns (uint256) {
        return _nonces[owner].current();
    }

    /**
     * @inheritdoc IERC20Permit
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @dev "Consume a nonce": return the current value and increment.
     *
     * _Available since v4.1._
     */
    function _useNonce(address owner) internal virtual returns (uint256 current) {
        Counters.Counter storage nonce = _nonces[owner];
        current = nonce.current();
        nonce.increment();
    }
}
IERC20Metadata.sol 28 lines
// 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);
}
IERC20Permit.sol 90 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
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].
     *
     * CAUTION: See Security Considerations above.
     */
    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);
}
SafeERC20.sol 143 lines
// 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));
    }
}
IERC721Receiver.sol 27 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}
Address.sol 244 lines
// 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);
        }
    }
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
Counters.sol 43 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)

pragma solidity ^0.8.0;

/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}
ShortStrings.sol 122 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.8;

import "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(_FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}
StorageSlot.sol 138 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
 * _Available since v4.9 for `string`, `bytes`._
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}
Strings.sol 85 lines
// 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));
    }
}
ECDSA.sol 217 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32")
            mstore(0x1c, hash)
            message := keccak256(0x00, 0x3c)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 0x42)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Data with intended validator, created from a
     * `validator` and `data` according to the version 0 of EIP-191.
     *
     * See {recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x00", validator, data));
    }
}
EIP712.sol 142 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.8;

import "./ECDSA.sol";
import "../ShortStrings.sol";
import "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * _Available since v3.4._
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant _TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    string private _nameFallback;
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {EIP-5267}.
     *
     * _Available since v4.9._
     */
    function eip712Domain()
        public
        view
        virtual
        override
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _name.toStringWithFallback(_nameFallback),
            _version.toStringWithFallback(_versionFallback),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }
}
ERC165.sol 29 lines
// 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;
    }
}
IERC165.sol 25 lines
// 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);
}
Math.sol 339 lines
// 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);
        }
    }
}
SafeMath.sol 215 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}
SignedMath.sol 43 lines
// 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);
        }
    }
}
EnumerableSet.sol 378 lines
// 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;
    }
}
IUniswapV3Pool.sol 24 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import './pool/IUniswapV3PoolImmutables.sol';
import './pool/IUniswapV3PoolState.sol';
import './pool/IUniswapV3PoolDerivedState.sol';
import './pool/IUniswapV3PoolActions.sol';
import './pool/IUniswapV3PoolOwnerActions.sol';
import './pool/IUniswapV3PoolEvents.sol';

/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
    IUniswapV3PoolImmutables,
    IUniswapV3PoolState,
    IUniswapV3PoolDerivedState,
    IUniswapV3PoolActions,
    IUniswapV3PoolOwnerActions,
    IUniswapV3PoolEvents
{

}
IUniswapV3SwapCallback.sol 21 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}
IUniswapV3PoolActions.sol 103 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
    /// @notice Sets the initial price for the pool
    /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
    /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
    function initialize(uint160 sqrtPriceX96) external;

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Collects tokens owed to a position
    /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
    /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
    /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
    /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
    /// @param recipient The address which should receive the fees collected
    /// @param tickLower The lower tick of the position for which to collect fees
    /// @param tickUpper The upper tick of the position for which to collect fees
    /// @param amount0Requested How much token0 should be withdrawn from the fees owed
    /// @param amount1Requested How much token1 should be withdrawn from the fees owed
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);

    /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
    /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
    /// @dev Fees must be collected separately via a call to #collect
    /// @param tickLower The lower tick of the position for which to burn liquidity
    /// @param tickUpper The upper tick of the position for which to burn liquidity
    /// @param amount How much liquidity to burn
    /// @return amount0 The amount of token0 sent to the recipient
    /// @return amount1 The amount of token1 sent to the recipient
    function burn(
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Swap token0 for token1, or token1 for token0
    /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
    /// @param recipient The address to receive the output of the swap
    /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
    /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
    /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
    /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
    /// @param data Any data to be passed through to the callback
    /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
    /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
    /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
    /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
    /// with 0 amount{0,1} and sending the donation amount(s) from the callback
    /// @param recipient The address which will receive the token0 and token1 amounts
    /// @param amount0 The amount of token0 to send
    /// @param amount1 The amount of token1 to send
    /// @param data Any data to be passed through to the callback
    function flash(
        address recipient,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external;

    /// @notice Increase the maximum number of price and liquidity observations that this pool will store
    /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
    /// the input observationCardinalityNext.
    /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}
IUniswapV3PoolDerivedState.sol 40 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
    /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
    /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
    /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
    /// you must call it with secondsAgos = [3600, 0].
    /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
    /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
    /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
    /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
    /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
    /// timestamp
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
    /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
    /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
    /// snapshot is taken and the second snapshot is taken.
    /// @param tickLower The lower tick of the range
    /// @param tickUpper The upper tick of the range
    /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
    /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
    /// @return secondsInside The snapshot of seconds per liquidity for the range
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        returns (
            int56 tickCumulativeInside,
            uint160 secondsPerLiquidityInsideX128,
            uint32 secondsInside
        );
}
IUniswapV3PoolEvents.sol 121 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
    /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
    /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
    /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
    /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
    event Initialize(uint160 sqrtPriceX96, int24 tick);

    /// @notice Emitted when liquidity is minted for a given position
    /// @param sender The address that minted the liquidity
    /// @param owner The owner of the position and recipient of any minted liquidity
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity minted to the position range
    /// @param amount0 How much token0 was required for the minted liquidity
    /// @param amount1 How much token1 was required for the minted liquidity
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when fees are collected by the owner of a position
    /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
    /// @param owner The owner of the position for which fees are collected
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount0 The amount of token0 fees collected
    /// @param amount1 The amount of token1 fees collected
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    /// @notice Emitted when a position's liquidity is removed
    /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
    /// @param owner The owner of the position for which liquidity is removed
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity to remove
    /// @param amount0 The amount of token0 withdrawn
    /// @param amount1 The amount of token1 withdrawn
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick
    );

    /// @notice Emitted by the pool for any flashes of token0/token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the tokens from flash
    /// @param amount0 The amount of token0 that was flashed
    /// @param amount1 The amount of token1 that was flashed
    /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
    /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
    event Flash(
        address indexed sender,
        address indexed recipient,
        uint256 amount0,
        uint256 amount1,
        uint256 paid0,
        uint256 paid1
    );

    /// @notice Emitted by the pool for increases to the number of observations that can be stored
    /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
    /// just before a mint/swap/burn.
    /// @param observationCardinalityNextOld The previous value of the next observation cardinality
    /// @param observationCardinalityNextNew The updated value of the next observation cardinality
    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld,
        uint16 observationCardinalityNextNew
    );

    /// @notice Emitted when the protocol fee is changed by the pool
    /// @param feeProtocol0Old The previous value of the token0 protocol fee
    /// @param feeProtocol1Old The previous value of the token1 protocol fee
    /// @param feeProtocol0New The updated value of the token0 protocol fee
    /// @param feeProtocol1New The updated value of the token1 protocol fee
    event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

    /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
    /// @param sender The address that collects the protocol fees
    /// @param recipient The address that receives the collected protocol fees
    /// @param amount0 The amount of token0 protocol fees that is withdrawn
    /// @param amount0 The amount of token1 protocol fees that is withdrawn
    event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}
IUniswapV3PoolImmutables.sol 35 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);

    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);

    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);

    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);

    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);

    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}
IUniswapV3PoolOwnerActions.sol 23 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
    /// @notice Set the denominator of the protocol's % share of the fees
    /// @param feeProtocol0 new protocol fee for token0 of the pool
    /// @param feeProtocol1 new protocol fee for token1 of the pool
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

    /// @notice Collect the protocol fee accrued to the pool
    /// @param recipient The address to which collected protocol fees should be sent
    /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
    /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
    /// @return amount0 The protocol fee collected in token0
    /// @return amount1 The protocol fee collected in token1
    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);
}
IUniswapV3PoolState.sol 116 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
    /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
    /// when accessed externally.
    /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
    /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
    /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
    /// boundary.
    /// observationIndex The index of the last oracle observation that was written,
    /// observationCardinality The current maximum number of observations stored in the pool,
    /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
    /// feeProtocol The protocol fee for both tokens of the pool.
    /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
    /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
    /// unlocked Whether the pool is currently locked to reentrancy
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal0X128() external view returns (uint256);

    /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal1X128() external view returns (uint256);

    /// @notice The amounts of token0 and token1 that are owed to the protocol
    /// @dev Protocol fees will never exceed uint128 max in either token
    function protocolFees() external view returns (uint128 token0, uint128 token1);

    /// @notice The currently in range liquidity available to the pool
    /// @dev This value has no relationship to the total liquidity across all ticks
    function liquidity() external view returns (uint128);

    /// @notice Look up information about a specific tick in the pool
    /// @param tick The tick to look up
    /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
    /// tick upper,
    /// liquidityNet how much liquidity changes when the pool price crosses the tick,
    /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
    /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
    /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
    /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
    /// secondsOutside the seconds spent on the other side of the tick from the current tick,
    /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
    /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
    /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
    /// a specific position.
    function ticks(int24 tick)
        external
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint256 feeGrowthOutside0X128,
            uint256 feeGrowthOutside1X128,
            int56 tickCumulativeOutside,
            uint160 secondsPerLiquidityOutsideX128,
            uint32 secondsOutside,
            bool initialized
        );

    /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
    function tickBitmap(int16 wordPosition) external view returns (uint256);

    /// @notice Returns the information about a position by the position's key
    /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
    /// @return _liquidity The amount of liquidity in the position,
    /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
    /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
    /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
    /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    /// @notice Returns data about a specific observation index
    /// @param index The element of the observations array to fetch
    /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
    /// ago, rather than at a specific index in the array.
    /// @return blockTimestamp The timestamp of the observation,
    /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
    /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
    /// Returns initialized whether the observation has been initialized and the values are safe to use
    function observations(uint256 index)
        external
        view
        returns (
            uint32 blockTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,
            bool initialized
        );
}
IQuoter.sol 51 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Quoter Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
    /// @notice Returns the amount out received for a given exact input swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee
    /// @param amountIn The amount of the first token to swap
    /// @return amountOut The amount of the last token that would be received
    function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);

    /// @notice Returns the amount out received for a given exact input but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountIn The desired input amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountOut The amount of `tokenOut` that would be received
    function quoteExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountOut);

    /// @notice Returns the amount in required for a given exact output swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
    /// @param amountOut The amount of the last token to receive
    /// @return amountIn The amount of first token required to be paid
    function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);

    /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountOut The desired output amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
    function quoteExactOutputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountOut,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountIn);
}
ISwapRouter.sol 67 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
Guardable.sol 86 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/**
 * @title Guardable
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (a guardian) that can be granted exclusive access to
 * specific functions.
 *
 * This module is essentially a renamed version of the OpenZeppelin Ownable contract.
 * The main difference is in terminology:
 * - 'owner' is renamed to 'guardian'
 * - 'ownership' concepts are renamed to 'watch' or 'guard'
 *
 * By default, the guardian account will be the one that deploys the contract. This
 * can later be changed with {transferWatch}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyGuardian`, which can be applied to your functions to restrict their use to
 * the guardian.
 */
abstract contract Guardable {
    address private _guardian;

    event WatchTransferred(address indexed previousGuardian, address indexed newGuardian);

    /**
     * @dev Initializes the contract setting the deployer as the initial guardian.
     */
    constructor() {
        _transferWatch(msg.sender);
    }

    /**
     * @dev Throws if called by any account other than the guardian.
     */
    modifier onlyGuardian() {
        _checkGuardian();
        _;
    }

    /**
     * @dev Returns the address of the current guardian.
     */
    function guardian() public view virtual returns (address) {
        return _guardian;
    }

    /**
     * @dev Throws if the sender is not the guardian.
     */
    function _checkGuardian() internal view virtual {
        require(guardian() == msg.sender, "Guardable: caller is not the guardian");
    }

    /**
     * @dev Leaves the contract without guardian. It will not be possible to call
     * `onlyGuardian` functions anymore. Can only be called by the current guardian.
     *
     * NOTE: Renouncing guardianship will leave the contract without a guardian,
     * thereby removing any functionality that is only available to the guardian.
     */
    function releaseGuard() public virtual onlyGuardian {
        _transferWatch(address(0));
    }

    /**
     * @dev Transfers guardianship of the contract to a new account (`newGuardian`).
     * Can only be called by the current guardian.
     */
    function transferWatch(address newGuardian) public virtual onlyGuardian {
        require(newGuardian != address(0), "Guardable: new guardian is the zero address");
        _transferWatch(newGuardian);
    }

    /**
     * @dev Transfers guardianship of the contract to a new account (`newGuardian`).
     * Internal function without access restriction.
     */
    function _transferWatch(address newGuardian) internal virtual {
        address oldGuardian = _guardian;
        _guardian = newGuardian;
        emit WatchTransferred(oldGuardian, newGuardian);
    }
}
Multicall.sol 220 lines
/**
 *Submitted for verification at optimistic.etherscan.io on 2022-03-09
*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

/// @title Multicall3
/// @notice Aggregate results from multiple function calls
/// @dev Multicall & Multicall2 backwards-compatible
/// @dev Aggregate methods are marked `payable` to save 24 gas per call
/// @author Michael Elliot <[email protected]>
/// @author Joshua Levine <[email protected]>
/// @author Nick Johnson <[email protected]>
/// @author Andreas Bigger <[email protected]>
/// @author Matt Solomon <[email protected]>
contract Multicall3 {
    struct Call {
        address target;
        bytes callData;
    }

    struct Call3 {
        address target;
        bool allowFailure;
        bytes callData;
    }

    struct Call3Value {
        address target;
        bool allowFailure;
        uint256 value;
        bytes callData;
    }

    struct Result {
        bool success;
        bytes returnData;
    }

    /// @notice Backwards-compatible call aggregation with Multicall
    /// @param calls An array of Call structs
    /// @return blockNumber The block number where the calls were executed
    /// @return returnData An array of bytes containing the responses
    function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) {
        blockNumber = block.number;
        uint256 length = calls.length;
        returnData = new bytes[](length);
        Call calldata call;
        for (uint256 i = 0; i < length;) {
            bool success;
            call = calls[i];
            (success, returnData[i]) = call.target.call(call.callData);
            require(success, "Multicall3: call failed");
            unchecked { ++i; }
        }
    }

    /// @notice Backwards-compatible with Multicall2
    /// @notice Aggregate calls without requiring success
    /// @param requireSuccess If true, require all calls to succeed
    /// @param calls An array of Call structs
    /// @return returnData An array of Result structs
    function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) {
        uint256 length = calls.length;
        returnData = new Result[](length);
        Call calldata call;
        for (uint256 i = 0; i < length;) {
            Result memory result = returnData[i];
            call = calls[i];
            (result.success, result.returnData) = call.target.call(call.callData);
            if (requireSuccess) require(result.success, "Multicall3: call failed");
            unchecked { ++i; }
        }
    }

    /// @notice Backwards-compatible with Multicall2
    /// @notice Aggregate calls and allow failures using tryAggregate
    /// @param calls An array of Call structs
    /// @return blockNumber The block number where the calls were executed
    /// @return blockHash The hash of the block where the calls were executed
    /// @return returnData An array of Result structs
    function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
        blockNumber = block.number;
        blockHash = blockhash(block.number);
        returnData = tryAggregate(requireSuccess, calls);
    }

    /// @notice Backwards-compatible with Multicall2
    /// @notice Aggregate calls and allow failures using tryAggregate
    /// @param calls An array of Call structs
    /// @return blockNumber The block number where the calls were executed
    /// @return blockHash The hash of the block where the calls were executed
    /// @return returnData An array of Result structs
    function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
        (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
    }

    /// @notice Aggregate calls, ensuring each returns success if required
    /// @param calls An array of Call3 structs
    /// @return returnData An array of Result structs
    function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) {
        uint256 length = calls.length;
        returnData = new Result[](length);
        Call3 calldata calli;
        for (uint256 i = 0; i < length;) {
            Result memory result = returnData[i];
            calli = calls[i];
            (result.success, result.returnData) = calli.target.call(calli.callData);
            assembly {
            // Revert if the call fails and failure is not allowed
            // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
                if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
                // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
                    mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                // set data offset
                    mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
                // set length of revert string
                    mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
                // set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
                    mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
                    revert(0x00, 0x64)
                }
            }
            unchecked { ++i; }
        }
    }

    /// @notice Aggregate calls with a msg value
    /// @notice Reverts if msg.value is less than the sum of the call values
    /// @param calls An array of Call3Value structs
    /// @return returnData An array of Result structs
    function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) {
        uint256 valAccumulator;
        uint256 length = calls.length;
        returnData = new Result[](length);
        Call3Value calldata calli;
        for (uint256 i = 0; i < length;) {
            Result memory result = returnData[i];
            calli = calls[i];
            uint256 val = calli.value;
            // Humanity will be a Type V Kardashev Civilization before this overflows - andreas
            // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
            unchecked { valAccumulator += val; }
            (result.success, result.returnData) = calli.target.call{value: val}(calli.callData);
            assembly {
            // Revert if the call fails and failure is not allowed
            // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
                if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
                // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
                    mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                // set data offset
                    mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
                // set length of revert string
                    mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
                // set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
                    mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
                    revert(0x00, 0x84)
                }
            }
            unchecked { ++i; }
        }
        // Finally, make sure the msg.value = SUM(call[0...i].value)
        require(msg.value == valAccumulator, "Multicall3: value mismatch");
    }

    /// @notice Returns the block hash for the given block number
    /// @param blockNumber The block number
    function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
        blockHash = blockhash(blockNumber);
    }

    /// @notice Returns the block number
    function getBlockNumber() public view returns (uint256 blockNumber) {
        blockNumber = block.number;
    }

    /// @notice Returns the block coinbase
    function getCurrentBlockCoinbase() public view returns (address coinbase) {
        coinbase = block.coinbase;
    }

    /// @notice Returns the block difficulty
    function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
        difficulty = block.prevrandao;
    }

    /// @notice Returns the block gas limit
    function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
        gaslimit = block.gaslimit;
    }

    /// @notice Returns the block timestamp
    function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
        timestamp = block.timestamp;
    }

    /// @notice Returns the (ETH) balance of a given address
    function getEthBalance(address addr) public view returns (uint256 balance) {
        balance = addr.balance;
    }

    /// @notice Returns the block hash of the last block
    function getLastBlockHash() public view returns (bytes32 blockHash) {
        unchecked {
            blockHash = blockhash(block.number - 1);
        }
    }

    /// @notice Gets the base fee of the given block
    /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain
    function getBasefee() public view returns (uint256 basefee) {
        basefee = block.basefee;
    }

    /// @notice Returns the chain id
    function getChainId() public view returns (uint256 chainid) {
        chainid = block.chainid;
    }
}
Base.sol 50 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "./BaseMath.sol";

/* Contains global system constants and common functions. */
contract Base is BaseMath {
    uint constant internal SECONDS_IN_ONE_MINUTE = 60;

    /*
     * Half-life of 12h. 12h = 720 min
     * (1/2) = d^720 => d = (1/2)^(1/720)
     */
    uint constant internal MINUTE_DECAY_FACTOR = 999037758833783000;

    /*
    * BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption.
    * Corresponds to (1 / ALPHA) in the white paper.
    */
    uint constant internal BETA = 2;

    uint constant public _100pct = 1000000000000000000; // 1e18 == 100%

    // Min net debt remains a system global due to its rationale for keeping SortedPositions relatively small
    uint constant public MIN_NET_DEBT = 1800e18;

    uint constant internal PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%

    // Gas compensation is not configurable per collateral type, as this is
    // more-so a chain specific consideration rather than collateral specific
    uint constant public GAS_COMPENSATION = 200e18;

    // A dynamic fee, which kicks in and acts as a floor if the custom min fee % attached to a collateral instance is too low.
    uint constant public DYNAMIC_BORROWING_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
    uint constant public DYNAMIC_REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%

    address internal positionControllerAddress;
    address internal gasPoolAddress;

    // Return the amount of Collateral to be drawn from a position's collateral and sent as gas compensation.
    function _getCollGasCompensation(uint _entireColl) internal pure returns (uint) {
        return _entireColl / PERCENT_DIVISOR;
    }

    function _requireUserAcceptsFee(uint _fee, uint _amount, uint _maxFeePercentage) internal pure {
        uint feePercentage = (_fee * DECIMAL_PRECISION) / _amount;
        require(feePercentage <= _maxFeePercentage, "Fee exceeded");
    }
}
StableMath.sol 116 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

library StableMath {
    uint internal constant DECIMAL_PRECISION = 1e18;

    /* Precision for Nominal ICR (independent of price). Rationale for the value:
     *
     * - Making it “too high” could lead to overflows.
     * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division. 
     *
     * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
     * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
     *
     */
    uint internal constant NICR_PRECISION = 1e20;

    function _min(uint _a, uint _b) internal pure returns (uint) {
        return (_a < _b) ? _a : _b;
    }

    function _max(uint _a, uint _b) internal pure returns (uint) {
        return (_a >= _b) ? _a : _b;
    }

    /* 
    * Multiply two decimal numbers and use normal rounding rules:
    * -round product up if 19'th mantissa digit >= 5
    * -round product down if 19'th mantissa digit < 5
    *
    * Used only inside the exponentiation, _decPow().
    */
    function decMul(uint x, uint y) internal pure returns (uint decProd) {
        uint prod_xy = x * y;
        decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
    }

    /* 
    * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
    * 
    * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity. 
    * 
    * Called by PositionManager._calcDecayedBaseRate
    *
    * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
    * "minutes in 1000 years": 60 * 24 * 365 * 1000
    * 
    * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be
    * negligibly different from just passing the cap, since: 
    *
    * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years
    * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible
    */
    function _decPow(uint _base, uint _minutes) internal pure returns (uint) {

        if (_minutes > 525600000) {_minutes = 525600000;}  // cap to avoid overflow

        if (_minutes == 0) {return DECIMAL_PRECISION;}

        uint y = DECIMAL_PRECISION;
        uint x = _base;
        uint n = _minutes;

        // Exponentiation-by-squaring
        while (n > 1) {
            if (n % 2 == 0) {
                x = decMul(x, x);
                n = n / 2;
            } else { // if (n % 2 != 0)
                y = decMul(x, y);
                x = decMul(x, x);
                n = (n - 1) / 2;
            }
        }

        return decMul(x, y);
    }

    function _getAbsoluteDifference(uint _a, uint _b) internal pure returns (uint) {
        return (_a >= _b) ? _a - _b : _b - _a;
    }

    function _adjustDecimals(uint _val, uint8 _collDecimals) internal pure returns (uint) {
        if (_collDecimals < 18) {
            return _val * (10 ** (18 - _collDecimals));
        } else if (_collDecimals > 18) {
            // Assuming _collDecimals won't exceed 25, this should be safe from overflow.
            return _val / (10 ** (_collDecimals - 18));
        } else {
            return _val;
        }
    }

    function _computeNominalCR(uint _coll, uint _debt, uint8 _collDecimals) internal pure returns (uint) {
        if (_debt > 0) {
            _coll = _adjustDecimals(_coll, _collDecimals);
            return (_coll * NICR_PRECISION) / _debt;
        }
            // Return the maximal value for uint256 if the Position has a debt of 0. Represents "infinite" CR.
        else { // if (_debt == 0)
            return type(uint256).max;
        }
    }

    function _computeCR(uint _coll, uint _debt, uint _price, uint8 _collDecimals) internal pure returns (uint) {
        // Check for zero debt to avoid division by zero
        if (_debt == 0) {
            return type(uint256).max; // Infinite CR since there's no debt.
        }

        _coll = _adjustDecimals(_coll, _collDecimals);
        uint newCollRatio = (_coll * _price) / _debt;
        return newCollRatio;
    }
}
Oracle.sol 173 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

// Uniswap
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";

// OpenZeppelin
import "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * @notice Adapted Uniswap V3 OracleLibrary computation to be compliant with Solidity 0.8.x and later.
 *
 * Documentation for Auditors:
 *
 * Solidity Version: Updated the Solidity version pragma to ^0.8.0. This change ensures compatibility
 * with Solidity version 0.8.x.
 *
 * Safe Arithmetic Operations: Solidity 0.8.x automatically checks for arithmetic overflows/underflows.
 * Therefore, the code no longer needs to use SafeMath library (or similar) for basic arithmetic operations.
 * This change simplifies the code and reduces the potential for errors related to manual overflow/underflow checking.
 *
 * Overflow/Underflow: With the introduction of automatic overflow/underflow checks in Solidity 0.8.x, the code is inherently
 * safer and less prone to certain types of arithmetic errors.
 *
 * Removal of SafeMath Library: Since Solidity 0.8.x handles arithmetic operations safely, the use of SafeMath library
 * is omitted in this update.
 *
 * Git-style diff for the `consult` function:
 *
 * ```diff
 * function consult(address pool, uint32 secondsAgo)
 *     internal
 *     view
 *     returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity)
 * {
 *     require(secondsAgo != 0, 'BP');
 *
 *     uint32[] memory secondsAgos = new uint32[](2);
 *     secondsAgos[0] = secondsAgo;
 *     secondsAgos[1] = 0;
 *
 *     (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) =
 *         IUniswapV3Pool(pool).observe(secondsAgos);
 *
 *     int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
 *     uint160 secondsPerLiquidityCumulativesDelta =
 *         secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];
 *
 * -   arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgo);
 * +   int56 secondsAgoInt56 = int56(uint56(secondsAgo));
 * +   arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgoInt56);
 *     // Always round to negative infinity
 * -   if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;
 * +   if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgoInt56 != 0)) arithmeticMeanTick--;
 *
 * -   uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max;
 * +   uint192 secondsAgoUint192 = uint192(secondsAgo);
 * +   uint192 secondsAgoX160 = secondsAgoUint192 * type(uint160).max;
 *     harmonicMeanLiquidity = uint128(secondsAgoX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32));
 * }
 * ```
 */

/// @title Oracle library
/// @notice Provides functions to integrate with V3 pool oracle
library OracleLibrary {
    /// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool
    /// @param pool Address of the pool that we want to observe
    /// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
    /// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp
    /// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp
    function consult(
        address pool,
        uint32 secondsAgo
    )
    internal
    view
    returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity)
    {
        require(secondsAgo != 0, "BP");

        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = secondsAgo;
        secondsAgos[1] = 0;

        (
            int56[] memory tickCumulatives,
            uint160[] memory secondsPerLiquidityCumulativeX128s
        ) = IUniswapV3Pool(pool).observe(secondsAgos);

        int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
        uint160 secondsPerLiquidityCumulativesDelta = secondsPerLiquidityCumulativeX128s[
                    1
            ] - secondsPerLiquidityCumulativeX128s[0];

        // Safe casting of secondsAgo to int56 for division
        int56 secondsAgoInt56 = int56(uint56(secondsAgo));
        arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgoInt56);
        // Always round to negative infinity
        if (
            tickCumulativesDelta < 0 &&
            (tickCumulativesDelta % secondsAgoInt56 != 0)
        ) arithmeticMeanTick--;

        // Safe casting of secondsAgo to uint192 for multiplication
        uint192 secondsAgoUint192 = uint192(secondsAgo);
        harmonicMeanLiquidity = uint128(
            (secondsAgoUint192 * uint192(type(uint160).max)) /
            (uint192(secondsPerLiquidityCumulativesDelta) << 32)
        );
    }

    /// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation
    /// @param pool Address of Uniswap V3 pool that we want to observe
    /// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool
    function getOldestObservationSecondsAgo(
        address pool
    ) internal view returns (uint32 secondsAgo) {
        (
            ,
            ,
            uint16 observationIndex,
            uint16 observationCardinality,
            ,
            ,

        ) = IUniswapV3Pool(pool).slot0();
        require(observationCardinality > 0, "NI");

        (uint32 observationTimestamp, , , bool initialized) = IUniswapV3Pool(
            pool
        ).observations((observationIndex + 1) % observationCardinality);

        // The next index might not be initialized if the cardinality is in the process of increasing
        // In this case the oldest observation is always in index 0
        if (!initialized) {
            (observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0);
        }

        secondsAgo = uint32(block.timestamp) - observationTimestamp;
    }

    /// @notice Given a tick and a token amount, calculates the amount of token received in exchange
    /// a slightly modified version of the UniSwap library getQuoteAtTick to accept a sqrtRatioX96 as input parameter
    /// @param sqrtRatioX96 The sqrt ration
    /// @param baseAmount Amount of token to be converted
    /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
    /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
    /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
    function getQuoteForSqrtRatioX96(
        uint160 sqrtRatioX96,
        uint256 baseAmount,
        address baseToken,
        address quoteToken
    ) internal pure returns (uint256 quoteAmount) {
        // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
        if (sqrtRatioX96 <= type(uint128).max) {
            uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
            quoteAmount = baseToken < quoteToken
                ? Math.mulDiv(ratioX192, baseAmount, 1 << 192)
                : Math.mulDiv(1 << 192, baseAmount, ratioX192);
        } else {
            uint256 ratioX128 = Math.mulDiv(
                sqrtRatioX96,
                sqrtRatioX96,
                1 << 64
            );
            quoteAmount = baseToken < quoteToken
                ? Math.mulDiv(ratioX128, baseAmount, 1 << 128)
                : Math.mulDiv(1 << 128, baseAmount, ratioX128);
        }
    }
}
PoolAddress.sol 93 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

/**
 * @notice Adapted Uniswap V3 pool address computation to be compliant with Solidity 0.8.x and later.
 * @dev Changes were made to address the stricter type conversion rules in newer Solidity versions.
 *      Original Uniswap V3 code directly converted a uint256 to an address, which is disallowed in Solidity 0.8.x.
 *      Adaptation Steps:
 *        1. The `pool` address is computed by first hashing pool parameters.
 *        2. The resulting `uint256` hash is then explicitly cast to `uint160` before casting to `address`.
 *           This two-step conversion process is necessary due to the Solidity 0.8.x restriction.
 *           Direct conversion from `uint256` to `address` is disallowed to prevent mistakes
 *           that can occur due to the size mismatch between the types.
 *        3. Added a require statement to ensure `token0` is less than `token1`, maintaining
 *           Uniswap's invariant and preventing pool address calculation errors.
 * @param factory The Uniswap V3 factory contract address.
 * @param key The PoolKey containing token addresses and fee tier.
 * @return pool The computed address of the Uniswap V3 pool.
 * @custom:modification Explicit type conversion from `uint256` to `uint160` then to `address`.
 *
 * function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) {
 *     require(key.token0 < key.token1);
 *     pool = address(
 *         uint160( // Explicit conversion to uint160 added for compatibility with Solidity 0.8.x
 *             uint256(
 *                 keccak256(
 *                     abi.encodePacked(
 *                         hex'ff',
 *                         factory,
 *                         keccak256(abi.encode(key.token0, key.token1, key.fee)),
 *                         POOL_INIT_CODE_HASH
 *                     )
 *                 )
 *             )
 *         )
 *     );
 * }
 */

/// @dev This code is copied from Uniswap V3 which uses an older compiler version.
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
    bytes32 internal constant POOL_INIT_CODE_HASH =
        0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;

    /// @notice The identifying key of the pool
    struct PoolKey {
        address token0;
        address token1;
        uint24 fee;
    }

    /// @notice Returns PoolKey: the ordered tokens with the matched fee levels
    /// @param tokenA The first token of a pool, unsorted
    /// @param tokenB The second token of a pool, unsorted
    /// @param fee The fee level of the pool
    /// @return Poolkey The pool details with ordered token0 and token1 assignments
    function getPoolKey(
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal pure returns (PoolKey memory) {
        if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
        return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
    }

    /// @notice Deterministically computes the pool address given the factory and PoolKey
    /// @param factory The Uniswap V3 factory contract address
    /// @param key The PoolKey
    /// @return pool The contract address of the V3 pool
    function computeAddress(
        address factory,
        PoolKey memory key
    ) internal pure returns (address pool) {
        require(key.token0 < key.token1);
        pool = address(
            uint160( // Convert uint256 to uint160 first
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(
                                abi.encode(key.token0, key.token1, key.fee)
                            ),
                            POOL_INIT_CODE_HASH
                        )
                    )
                )
            )
        );
    }
}
TickMath.sol 308 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

/**
 * @notice Adapted Uniswap V3 TickMath library computation to be compliant with Solidity 0.8.x and later.
 *
 * Documentation for Auditors:
 *
 * Solidity Version: Updated the Solidity version pragma to ^0.8.0. This change ensures compatibility
 * with Solidity version 0.8.x.
 *
 * Safe Arithmetic Operations: Solidity 0.8.x automatically checks for arithmetic overflows/underflows.
 * Therefore, the code no longer needs to use the SafeMath library (or similar) for basic arithmetic operations.
 * This change simplifies the code and reduces the potential for errors related to manual overflow/underflow checking.
 *
 * Explicit Type Conversion: The explicit conversion of `MAX_TICK` from `int24` to `uint256` in the `require` statement
 * is safe and necessary for comparison with `absTick`, which is a `uint256`. This conversion is compliant with
 * Solidity 0.8.x's type system and does not introduce any arithmetic risk.
 *
 * Overflow/Underflow: With the introduction of automatic overflow/underflow checks in Solidity 0.8.x, the code is inherently
 * safer and less prone to certain types of arithmetic errors.
 *
 * Removal of SafeMath Library: Since Solidity 0.8.x handles arithmetic operations safely, the use of the SafeMath library
 * is omitted in this update.
 *
 * Git-style diff for the TickMath library:
 *
 * ```diff
 * - pragma solidity >=0.5.0 <0.8.0;
 * + pragma solidity ^0.8.0;
 *
 *   function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
 *       uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
 * -     require(absTick <= uint256(MAX_TICK), 'T');
 * +     require(absTick <= uint256(int256(MAX_TICK)), 'T'); // Explicit type conversion for Solidity 0.8.x compatibility
 *       // ... (rest of the function)
 *   }
 *
 * function getTickAtSqrtRatio(
 *     uint160 sqrtPriceX96
 * ) internal pure returns (int24 tick) {
 *     // [Code for calculating the tick based on sqrtPriceX96 remains unchanged]
 *
 * -   tick = tickLow == tickHi
 * -       ? tickLow
 * -       : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96
 * -       ? tickHi
 * -       : tickLow;
 * +   if (tickLow == tickHi) {
 * +       tick = tickLow;
 * +   } else {
 * +       tick = (getSqrtRatioAtTick(tickHi) <= sqrtPriceX96) ? tickHi : tickLow;
 * +   }
 * }
 * ```
 *
 * Note: Other than the pragma version change and the explicit type conversion in the `require` statement, the original functions
 * within the TickMath library are compatible with Solidity 0.8.x without requiring any further modifications. This is due to
 * the fact that the logic within these functions already adheres to safe arithmetic practices and does not involve operations
 * that would be affected by the 0.8.x compiler's built-in checks.
 */

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO =
    1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(
        int24 tick
    ) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0
            ? uint256(-int256(tick))
            : uint256(int256(tick));
        require(absTick <= uint256(int256(MAX_TICK)), "T"); // Explicit type conversion for Solidity 0.8.x compatibility

        uint256 ratio = absTick & 0x1 != 0
            ? 0xfffcb933bd6fad37aa2d162d1a594001
            : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0)
            ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0)
            ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0)
            ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0)
            ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0)
            ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0)
            ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0)
            ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0)
            ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0)
            ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0)
            ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0)
            ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0)
            ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0)
            ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0)
            ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0)
            ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0)
            ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0)
            ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0)
            ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0)
            ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
        // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
        // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160(
            (ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)
        );
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(
        uint160 sqrtPriceX96
    ) internal pure returns (int24 tick) {
        // second inequality must be < because the price can never reach the price at the max tick
        require(
            sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO,
            "R"
        );
        uint256 ratio = uint256(sqrtPriceX96) << 32;

        uint256 r = ratio;
        uint256 msb = 0;

        assembly {
            let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(5, gt(r, 0xFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(4, gt(r, 0xFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(3, gt(r, 0xFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(2, gt(r, 0xF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(1, gt(r, 0x3))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := gt(r, 0x1)
            msb := or(msb, f)
        }

        if (msb >= 128) r = ratio >> (msb - 127);
        else r = ratio << (127 - msb);

        int256 log_2 = (int256(msb) - 128) << 64;

        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(63, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(62, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(61, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(60, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(59, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(58, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(57, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(56, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(55, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(54, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(53, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(52, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(51, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(50, f))
        }

        int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

        int24 tickLow = int24(
            (log_sqrt10001 - 3402992956809132418596140100660247210) >> 128
        );
        int24 tickHi = int24(
            (log_sqrt10001 + 291339464771989622907027621153398088495) >> 128
        );

        // Adjusted logic for determining the tick
        if (tickLow == tickHi) {
            tick = tickLow;
        } else {
            tick = (getSqrtRatioAtTick(tickHi) <= sqrtPriceX96)
                ? tickHi
                : tickLow;
        }
    }
}
BackstopPoolIncentives.sol 190 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../Guardable.sol";
import "../interfaces/IFeeToken.sol";
import "../interfaces/IFeeTokenMinter.sol";
import "../common/BaseMath.sol";
import "../common/StableMath.sol";

interface IBackstopPoolIncentives {
    event TotalFeeTokensIssuedUpdated(uint _totalFeeTokensIssued);

    function setAddresses(address _feeTokenAddress, address _backstopPoolAddress, address _feeTokenMinterAddress) external;
    function issueFeeTokens() external returns (uint);
    function sendFeeTokens(address _account, uint _feeTokenAmount) external;
}

/**
 * @title BackstopPoolIncentives
 * @dev Contract for managing the issuance and distribution of FeeTokens as incentives for the Backstop Pool.
 *
 * Key features:
 *      1. Token Issuance: Implements a decay curve for FeeToken issuance over time - 50% each year.
 *      2. Activation Mechanism: Allows for the activation of backstop rewards by the guardian, initially they are OFF.
 *      3. Re-staking Requirement: Requires users to restake after reward activation, so that rewards from previous deposits are not rewarded.
 *      4. Immutable Supply Schedule: Guaranteed reduction in supply over time, even if rewards are not activated. If rewards are not needed, they should converge to zero.
 */
contract BackstopPoolIncentives is IBackstopPoolIncentives, Ownable, BaseMath, Guardable {
    //==================================================================//
    //-------------------------- CONSTANTS -----------------------------//
    //==================================================================//

    /// @dev Number of seconds in one minute
    uint constant public SECONDS_IN_ONE_MINUTE = 60;

    /**
     * @dev The issuance factor F determines the curvature of the issuance curve.
     *
     * For 50% of remaining tokens issued each year, with minutes as time units, we have:
     * F ** 525600 = 0.5
     *
     * Re-arranging:
     * 525600 * ln(F) = ln(0.5)
     * F = 0.5 ** (1/525600)
     * F = 0.999998681227695000
     */
    uint constant public ISSUANCE_FACTOR = 999998681227695000;

    /// @dev Maximum tokens allocated for reward (200 million)
    uint constant public incentivesSupplyCap = 200_000_000e18;

    //==================================================================//
    //-------------------------- INTERFACES ----------------------------//
    //==================================================================//

    IFeeToken public feeToken;
    IFeeTokenMinter public feeTokenMinter;

    //==================================================================//
    //----------------------- STATE VARIABLES --------------------------//
    //==================================================================//

    /// @dev Address of the Backstop Pool contract
    address public backstopPoolAddress;

    /// @dev Total amount of FeeTokens issued as incentives up to this point
    uint public totalFeeTokensIssued;

    /// @dev Timestamp of contract deployment
    uint public immutable deploymentTime;

    /// @dev Flag indicating if backstop rewards are active
    bool public backstopRewardsActive;

    /// @dev Mapping to track if an account has restaked since reward activation
    mapping(address => bool) public restakedSinceActivation;

    //==================================================================//
    //------------------------- CONSTRUCTOR ----------------------------//
    //==================================================================//

    /**
     * @dev Constructor just sets the deployment time
     */
    constructor() {
        deploymentTime = block.timestamp;
    }

    //==================================================================//
    //----------------------- SETUP FUNCTIONS --------------------------//
    //==================================================================//

    /**
     * @dev Sets the addresses for the contract dependencies, and renounces ownership
     * @param _feeTokenAddress Address of the FeeToken contract
     * @param _backstopPoolAddress Address of the BackstopPool contract
     * @param _feeTokenMinterAddress Address of the FeeTokenMinter contract
     */
    function setAddresses(
        address _feeTokenAddress,
        address _backstopPoolAddress,
        address _feeTokenMinterAddress
    ) external onlyOwner override {
        require(_feeTokenAddress != address(0), "_feeTokenAddress is the null address");
        require(_backstopPoolAddress != address(0), "_backstopPoolAddress is the null address");
        require(_feeTokenMinterAddress != address(0), "_feeTokenMinterAddress is the null address");

        feeToken = IFeeToken(_feeTokenAddress);
        backstopPoolAddress = _backstopPoolAddress;
        feeTokenMinter = IFeeTokenMinter(_feeTokenMinterAddress);
        renounceOwnership();
    }

    /**
     * @dev Activates the backstop rewards.  Can only be called by the guardian.
     */
    function activateBackstopIncentives() external onlyGuardian {
        require(!backstopRewardsActive, "Backstop rewards are already being dispersed");
        backstopRewardsActive = true;
    }

    //==================================================================//
    //----------------- EXTERNAL MUTATIVE FUNCTIONS --------------------//
    //==================================================================//

    /**
     * @dev Issues FeeTokens based on the current issuance curve
     * @return The amount of FeeTokens issued
     */
    function issueFeeTokens() external override returns (uint) {
        _requireCallerIsBackstopPool();
        uint latestTotalFeeTokensIssued = (incentivesSupplyCap * _getCumulativeIssuanceFraction()) / DECIMAL_PRECISION;
        uint issuance = latestTotalFeeTokensIssued - totalFeeTokensIssued;
        totalFeeTokensIssued = latestTotalFeeTokensIssued;
        emit TotalFeeTokensIssuedUpdated(latestTotalFeeTokensIssued);
        return issuance;
    }

    /**
     * @dev Initiates a FeeToken vest for an account, if backstop rewards were activated, and the user restaked
     * @param _account The address of the account to receive FeeTokens
     * @param _feeTokenAmount The amount of FeeTokens to vest
     */
    function sendFeeTokens(address _account, uint _feeTokenAmount) external override {
        _requireCallerIsBackstopPool();
        if (backstopRewardsActive) {
            // Require the user to restake following reward activation,
            // otherwise historic rewards will be paid out instead of being orphaned as intended.
            if(restakedSinceActivation[_account]) {
                // They retouched their stake, and as a result orphaned their old rewards.
                // It's now safe to credit their rewards
                if(_feeTokenAmount > 0) {
                    feeTokenMinter.appendVestingEntry(_account, _feeTokenAmount);
                }
            } else {
                restakedSinceActivation[_account] = true;
            }
        }
    }

    //==================================================================//
    //---------------------- INTERNAL FUNCTIONS ------------------------//
    //==================================================================//

    /**
     * @dev Calculates the cumulative issuance fraction (1-f^t)
     * @return The cumulative issuance fraction
     */
    function _getCumulativeIssuanceFraction() internal view returns (uint) {
        // Get the time passed since deployment
        uint timePassedInMinutes = (block.timestamp - deploymentTime) / SECONDS_IN_ONE_MINUTE;

        // f^t
        uint power = StableMath._decPow(ISSUANCE_FACTOR, timePassedInMinutes);

        //  (1 - f^t)
        uint cumulativeIssuanceFraction = uint(DECIMAL_PRECISION) - power;
        assert(cumulativeIssuanceFraction <= DECIMAL_PRECISION); // must be in range [0,1]
        return cumulativeIssuanceFraction;
    }

    /**
     * @dev Ensures that the caller is the BackstopPool contract
     */
    function _requireCallerIsBackstopPool() internal view {
        require(msg.sender == backstopPoolAddress, "IncentivesIssuance: caller is not BP");
    }
}
StableLpIncentives.sol 354 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../Guardable.sol";
import "../interfaces/IFeeTokenMinter.sol";
import "../common/StableMath.sol";

/**
 * @title ILPTokenWrapper
 * @dev Interface for the LPTokenWrapper contract
 */
interface ILPTokenWrapper {
    function stake(uint256 amount) external;
    function withdraw(uint256 amount) external;
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
}

/**
 * @title LPTokenWrapper
 * @dev Base contract containing the basic staking functionality
 */
contract LPTokenWrapper is ILPTokenWrapper {
    using SafeERC20 for IERC20;

    IERC20 public uniToken;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    function stake(uint256 amount) public virtual override {
        _totalSupply = _totalSupply + amount;
        _balances[msg.sender] = _balances[msg.sender] + amount;
        uniToken.safeTransferFrom(msg.sender, address(this), amount);
    }

    function withdraw(uint256 amount) public virtual override {
        _totalSupply = _totalSupply - amount;
        _balances[msg.sender] = _balances[msg.sender] - amount;
        uniToken.safeTransfer(msg.sender, amount);
    }
}

/**
 * @title IStableLpIncentives
 * @dev Interface for the StableLpIncentives contract
 */
interface IStableLpIncentives {
    function setParams(address _uniTokenAddress, uint _duration, address _feeTokenMinter) external;
    function lastTimeRewardApplicable() external view returns (uint256);
    function rewardPerToken() external view returns (uint256);
    function earned(address account) external view returns (uint256);
    function withdrawAndClaim() external;
    function claimReward() external;
}

/**
 * @title StableLpIncentives
 * @dev Contract for managing liquidity provider incentives for Stable/<BASE> Uniswap pool
 *
 * Key features:
 *      1. Staking: LPs can stake UNIv2 LP tokens to earn rewards.
 *      2. Reward Distribution: Implements a time-based reward distribution mechanism.
 *      3. Claiming: LPs can claim accrued rewards at any time.
 *      4. Flexible Duration: Adjusts reward period if total staked amount becomes zero.
 *      5. Activation Control: Allows for one-time activation of LP rewards by a guardian.
 */
contract StableLpIncentives is LPTokenWrapper, Ownable, IStableLpIncentives, Guardable {
    // Adapted from: https://github.com/Synthetixio/Unipool/blob/master/contracts/Unipool.sol
    // Some more useful references:
    // Synthetix proposal: https://sips.synthetix.io/sips/sip-31
    // Original audit: https://github.com/sigp/public-audits/blob/master/synthetix/unipool/review.pdf
    // Incremental changes (commit by commit) from the original to this version: https://github.com/liquity/dev/pull/271

    /*
     * On deployment a new Uniswap pool will be created for the pair USDx/ETH on UniV2 and its token will be set here.
     * Essentially the way it works is:
     *
     * - Liquidity providers add funds to the Uniswap pool, and get UNIv2 LP tokens in exchange
     * - Liquidity providers stake those UNIv2 LP tokens into StableLpIncentives rewards contract
     * - Liquidity providers accrue rewards, proportional to the amount of staked tokens and staking time
     * - Liquidity providers can claim their rewards when they want
     * - Liquidity providers can unstake UNIv2 LP tokens to exit the program (i.e., stop earning rewards) when they want
     *
     * Funds for rewards will only be notified when rewards are activated.  They are never added to the contract.
     * Instead, this contract has rights to trigger vesting entries in the FeeTokenMinter contract.
     *
     * If at some point the total amount of staked tokens is zero, the clock will be “stopped”,
     * so the period will be extended by the time during which the staking pool is empty,
     * in order to avoid getting ORX rewards orphaned.
     *
     * Start time for the program will be the call to activateLpRewards event.
     */

    //==================================================================//
    //----------------------- STATE VARIABLES --------------------------//
    //==================================================================//

    /// @dev Duration of the reward distribution period
    uint256 public duration;

    /// @dev Interface for the FeeTokenMinter contract
    IFeeTokenMinter public feeTokenMinter;

    /// @dev Flag indicating if LP rewards have been activated
    bool public lpRewardsActivated;

    /// @dev Timestamp when the reward period ends
    uint256 public periodFinish = 0;

    /// @dev Rate at which rewards are distributed per second
    uint256 public rewardRate = 0;

    /// @dev Last time the reward states were updated
    uint256 public lastUpdateTime;

    /// @dev Accumulated rewards per token, stored
    uint256 public rewardPerTokenStored;

    /// @dev Mapping of user address to their last recorded rewardPerToken
    mapping(address => uint256) public userRewardPerTokenPaid;

    /// @dev Mapping of user address to their unclaimed rewards
    mapping(address => uint256) public rewards;

    //==================================================================//
    //--------------------------- EVENTS -------------------------------//
    //==================================================================//

    event UniTokenAddressChanged(address _uniTokenAddress);
    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);

    //==================================================================//
    //-------------------- ADMIN/SETUP FUNCTIONS -----------------------//
    //==================================================================//

    /**
     * @dev Sets the initial parameters for the contract
     * @param _uniTokenAddress Address of the UNIv2 LP token
     * @param _duration Duration of the reward distribution period
     * @param _feeTokenMinter Address of the FeeTokenMinter contract
     */
    function setParams(address _uniTokenAddress, uint _duration, address _feeTokenMinter) external override onlyOwner {
        require(_uniTokenAddress != address(0), "_uniTokenAddress is the null address");
        require(_feeTokenMinter != address(0), "_feeTokenMinter is the null address");

        uniToken = IERC20(_uniTokenAddress);
        duration = _duration;
        feeTokenMinter = IFeeTokenMinter(_feeTokenMinter);
        emit UniTokenAddressChanged(_uniTokenAddress);
        renounceOwnership();
    }

    /**
     * @dev Activates the LP rewards program. Can only be called once by the guardian.
     */
    function activateLpRewards() external onlyGuardian {
        // LQTY fork notes: Balance of contract is not checked, as unlike in LQTY which mints tokens to this address
        // at deployment time, the FeeTokenMinter contract has exclusive rights to mint ORX with vesting entries.
        // What's important here, is that StableLpIncentives cannot create more than 50_000_000e18 total units
        // as part of vesting entries.
        _notifyRewardAmount(50_000_000e18, duration);
        require(!lpRewardsActivated, "Can only be activated once");
        lpRewardsActivated = true;
    }

    //==================================================================//
    //-------------------------- MODIFIERS -----------------------------//
    //==================================================================//

    /// @dev Ensures that the LP rewards program has been activated
    modifier onlyWhenActive {
        require(lpRewardsActivated, "LP Rewards have not yet been activated");
        _;
    }

    //==================================================================//
    //----------------- EXTERNAL VIEW FUNCTIONS ------------------------//
    //==================================================================//

    /**
     * @dev Returns the last timestamp where rewards are applicable
     * @return The smaller of current timestamp and period finish time
     */
    function lastTimeRewardApplicable() public view override returns (uint256) {
        return StableMath._min(block.timestamp, periodFinish);
    }

    /**
     * @dev Calculates the amount of rewards per staked token
     * @return The reward amount per token staked
     */
    function rewardPerToken() public view override returns (uint256) {
        if (totalSupply() == 0) {
            return rewardPerTokenStored;
        }

        return rewardPerTokenStored + (
            ((((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate) * 1e18) / totalSupply())
        );
    }

    /**
     * @dev Calculates the amount of rewards an account has earned
     * @param account Address of the account to check
     * @return The amount of rewards earned by the account
     */
    function earned(address account) public view override returns (uint256) {
        return (((balanceOf(account) * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18) + rewards[account]);
    }

    //==================================================================//
    //----------------- EXTERNAL MUTATIVE FUNCTIONS --------------------//
    //==================================================================//

    /**
     * @dev Allows users to stake LP tokens
     * @param amount Amount of LP tokens to stake
     */
    function stake(uint256 amount) public onlyWhenActive override {
        require(amount > 0, "Cannot stake 0");
        require(address(uniToken) != address(0), "Liquidity Pool Token has not been set yet");

        _updatePeriodFinish();
        _updateAccountReward(msg.sender);

        super.stake(amount);

        emit Staked(msg.sender, amount);
    }

    /**
     * @dev Allows users to withdraw staked LP tokens
     * @param amount Amount of LP tokens to withdraw
     */
    function withdraw(uint256 amount) public onlyWhenActive override {
        require(amount > 0, "Cannot withdraw 0");
        require(address(uniToken) != address(0), "Liquidity Pool Token has not been set yet");

        _updateAccountReward(msg.sender);

        super.withdraw(amount);

        emit Withdrawn(msg.sender, amount);
    }

    /**
     * @dev Allows users to withdraw all staked tokens and claim rewards in one transaction
     */
    function withdrawAndClaim() external onlyWhenActive override {
        withdraw(balanceOf(msg.sender));
        claimReward();
    }

    /**
     * @dev Allows users to claim their accrued rewards
     */
    function claimReward() public onlyWhenActive override {
        require(address(uniToken) != address(0), "Liquidity Pool Token has not been set yet");

        _updatePeriodFinish();
        _updateAccountReward(msg.sender);

        uint256 reward = earned(msg.sender);

        require(reward > 0, "Nothing to claim");

        rewards[msg.sender] = 0;
        feeTokenMinter.appendVestingEntry(msg.sender, reward);
        emit RewardPaid(msg.sender, reward);
    }

    //==================================================================//
    //---------------------- INTERNAL FUNCTIONS ------------------------//
    //==================================================================//

    /**
     * @dev Initializes the reward distribution
     * @param _reward Total amount of rewards to distribute
     * @param _duration Duration of the distribution period
     */
    function _notifyRewardAmount(uint256 _reward, uint256 _duration) internal {
        assert(_reward == 50_000_000e18);
        assert(periodFinish == 0);

        _updateReward();

        rewardRate = _reward / _duration;

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp + _duration;
        emit RewardAdded(_reward);
    }

    /**
     * @dev Adjusts the period finish time if total supply becomes zero
     */
    function _updatePeriodFinish() internal {
        if (totalSupply() == 0) {
            assert(periodFinish > 0);
            /*
             * If the finish period has been reached (but there are remaining rewards due to zero stake),
             * to get the new finish date we must add to the current timestamp the difference between
             * the original finish time and the last update, i.e.:
             *
             * periodFinish = block.timestamp + (periodFinish - lastUpdateTime);
             *
             * If we have not reached the end yet, we must extend it by adding to it the difference between
             * the current timestamp and the last update (the period where the supply has been empty), i.e.:
             *
             * periodFinish = periodFinish + (block.timestamp - astUpdateTime);
             *
             * Both formulas are equivalent.
             */
            periodFinish = periodFinish + (block.timestamp - lastUpdateTime);
        }
    }

    /**
     * @dev Updates the stored reward per token and last update time
     */
    function _updateReward() internal {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
    }

    /**
     * @dev Updates the reward state for a specific account
     * @param account Address of the account to update
     */
    function _updateAccountReward(address account) internal {
        _updateReward();

        assert(account != address(0));

        rewards[account] = earned(account);
        userRewardPerTokenPaid[account] = rewardPerTokenStored;
    }
}
IActivePool.sol 37 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "./IPool.sol";
import "./ICanReceiveCollateral.sol";

/// @title IActivePool Interface
/// @notice Interface for the ActivePool contract which manages the main collateral pool
interface IActivePool is IPool, ICanReceiveCollateral {
    /// @notice Emitted when the stable debt in the ActivePool is updated
    /// @param _STABLEDebt The new total stable debt amount
    event ActivePoolStableDebtUpdated(uint _STABLEDebt);

    /// @notice Emitted when the collateral balance in the ActivePool is updated
    /// @param _Collateral The new total collateral amount
    event ActivePoolCollateralBalanceUpdated(uint _Collateral);

    /// @notice Sends collateral from the ActivePool to a specified account
    /// @param _account The address of the account to receive the collateral
    /// @param _amount The amount of collateral to send
    function sendCollateral(address _account, uint _amount) external;

    /// @notice Sets the addresses of connected contracts and components
    /// @param _positionControllerAddress Address of the PositionController contract
    /// @param _positionManagerAddress Address of the PositionManager contract
    /// @param _backstopPoolAddress Address of the BackstopPool contract
    /// @param _defaultPoolAddress Address of the DefaultPool contract
    /// @param _collateralAssetAddress Address of the collateral asset token
    function setAddresses(
        address _positionControllerAddress,
        address _positionManagerAddress,
        address _backstopPoolAddress,
        address _defaultPoolAddress,
        address _collateralAssetAddress
    ) external;
}
IBackstopPool.sol 149 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IBackstopPool Interface
/// @notice Interface for the BackstopPool contract which manages deposits and collateral gains
interface IBackstopPool {
    /// @notice Struct to represent collateral gains for a specific asset
    struct CollateralGain {
        address asset;
        uint gains;
    }

    /// @notice Emitted when the collateral balance of the BackstopPool is updated
    /// @param asset The address of the collateral asset
    /// @param _newBalance The new balance of the collateral
    event BackstopPoolCollateralBalanceUpdated(address asset, uint _newBalance);

    /// @notice Emitted when the stable token balance of the BackstopPool is updated
    /// @param _newBalance The new balance of stable tokens
    event BackstopPoolStableBalanceUpdated(uint _newBalance);

    /// @notice Emitted when the product P is updated
    /// @param _P The new value of P
    event P_Updated(uint _P);

    /// @notice Emitted when the sum S is updated for a specific collateral asset
    /// @param collateralAsset The address of the collateral asset
    /// @param _S The new value of S
    /// @param _epoch The current epoch
    /// @param _scale The current scale
    event S_Updated(address collateralAsset, uint _S, uint128 _epoch, uint128 _scale);

    /// @notice Emitted when the sum G is updated
    /// @param _G The new value of G
    /// @param _epoch The current epoch
    /// @param _scale The current scale
    event G_Updated(uint _G, uint128 _epoch, uint128 _scale);

    /// @notice Emitted when the current epoch is updated
    /// @param _currentEpoch The new current epoch
    event EpochUpdated(uint128 _currentEpoch);

    /// @notice Emitted when the current scale is updated
    /// @param _currentScale The new current scale
    event ScaleUpdated(uint128 _currentScale);

    /// @notice Emitted when a depositor's snapshot is updated
    /// @param _depositor The address of the depositor
    /// @param _asset The address of the asset
    /// @param _P The current value of P
    /// @param _S The current value of S
    /// @param _G The current value of G
    event DepositSnapshotUpdated(address indexed _depositor, address indexed _asset, uint _P, uint _S, uint _G);

    /// @notice Emitted when a user's deposit amount changes
    /// @param _depositor The address of the depositor
    /// @param _newDeposit The new deposit amount
    event UserDepositChanged(address indexed _depositor, uint _newDeposit);

    /// @notice Emitted when collateral gains are withdrawn
    /// @param _depositor The address of the depositor
    /// @param gains An array of CollateralGain structs representing the gains
    /// @param _stableLoss The amount of stable tokens lost
    event CollateralGainsWithdrawn(address indexed _depositor, IBackstopPool.CollateralGain[] gains, uint _stableLoss);

    /// @notice Emitted when collateral is sent to an address
    /// @param asset The address of the collateral asset
    /// @param _to The recipient address
    /// @param _amount The amount of collateral sent
    event CollateralSent(address indexed asset, address indexed _to, uint _amount);

    /// @notice Emitted when fee tokens are paid to a depositor
    /// @param _depositor The address of the depositor
    /// @param _feeToken The amount of fee tokens paid
    event FeeTokenPaidToDepositor(address indexed _depositor, uint _feeToken);

    /// @notice Sets the addresses of connected contracts
    /// @param _collateralController The address of the CollateralController contract
    /// @param _stableTokenAddress The address of the StableToken contract
    /// @param _positionController The address of the PositionController contract
    /// @param _incentivesIssuance The address of the IncentivesIssuance contract
    function setAddresses(address _collateralController, address _stableTokenAddress, address _positionController, address _incentivesIssuance) external;

    /// @notice Allows a user to provide stable tokens to the BackstopPool
    /// @param _amount The amount of stable tokens to provide
    function provideToBP(uint _amount) external;

    /// @notice Allows a user to withdraw stable tokens from the BackstopPool
    /// @param _amount The amount of stable tokens to withdraw
    function withdrawFromBP(uint _amount) external;

    /// @notice Allows a user to withdraw collateral gains to their position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _upperHint The upper hint for position insertion
    /// @param _lowerHint The lower hint for position insertion
    function withdrawCollateralGainToPosition(address asset, uint8 version, address _upperHint, address _lowerHint) external;

    /// @notice Offsets debt with collateral
    /// @param collateralAsset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _debt The amount of debt to offset
    /// @param _coll The amount of collateral to add
    function offset(address collateralAsset, uint8 version, uint _debt, uint _coll) external;

    /// @notice Gets the total amount of a specific collateral in the BackstopPool
    /// @param asset The address of the collateral asset
    /// @return The amount of collateral
    function getCollateral(address asset) external view returns (uint);

    /// @notice Gets the total amount of stable token deposits in the BackstopPool
    /// @return The total amount of stable token deposits
    function getTotalStableDeposits() external view returns (uint);

    /// @notice Gets the collateral gains for a depositor
    /// @param _depositor The address of the depositor
    /// @return An array of CollateralGain structs representing the gains
    function getDepositorCollateralGains(address _depositor) external view returns (IBackstopPool.CollateralGain[] memory);

    /// @notice Gets the collateral gain for a specific asset and depositor
    /// @param asset The address of the collateral asset
    /// @param _depositor The address of the depositor
    /// @return The amount of collateral gain
    function getDepositorCollateralGain(address asset, address _depositor) external view returns (uint);

    /// @notice Gets the compounded stable deposit for a depositor
    /// @param _depositor The address of the depositor
    /// @return The compounded stable deposit amount
    function getCompoundedStableDeposit(address _depositor) external view returns (uint);

    /// @notice Gets the sum S for a specific asset, epoch, and scale
    /// @param asset The address of the collateral asset
    /// @param epoch The epoch number
    /// @param scale The scale number
    /// @return The sum S
    function getEpochToScaleToSum(address asset, uint128 epoch, uint128 scale) external view returns(uint);

    /// @notice Gets the fee token gain for a depositor
    /// @param _depositor The address of the depositor
    /// @return The amount of fee token gain
    function getDepositorFeeTokenGain(address _depositor) external view returns (uint);

    /// @notice Gets the sum S from the deposit snapshot for a specific user and asset
    /// @param user The address of the user
    /// @param asset The address of the asset
    /// @return The sum S from the deposit snapshot
    function getDepositSnapshotToAssetToSum(address user, address asset) external view returns(uint);
}
ICanReceiveCollateral.sol 8 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

// Common interface for the contracts which need internal collateral counters to be updated.
interface ICanReceiveCollateral {
    function receiveCollateral(address asset, uint amount) external;
}
ICollateralController.sol 321 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "./IActivePool.sol";
import "./ICollateralSurplusPool.sol";
import "./IDefaultPool.sol";
import "./IPriceFeed.sol";
import "./ISortedPositions.sol";
import "./IPositionManager.sol";

/// @title ICollateralController Interface
/// @notice Interface for the CollateralController contract which manages multiple collateral types and their settings
interface ICollateralController {
    /// @notice Emitted when the redemption cooldown requirement is changed
    /// @param newRedemptionCooldownRequirement The new cooldown period for redemptions
    event RedemptionCooldownRequirementChanged(uint newRedemptionCooldownRequirement);

    /// @notice Gets the address of the guardian
    /// @return The address of the guardian
    function getGuardian() external view returns (address);

    /// @notice Structure to hold redemption settings for a collateral type
    struct RedemptionSettings {
        uint256 redemptionCooldownPeriod;
        uint256 redemptionGracePeriod;
        uint256 maxRedemptionPoints;
        uint256 availableRedemptionPoints;
        uint256 redemptionRegenerationRate;
        uint256 lastRedemptionRegenerationTimestamp;
    }

    /// @notice Structure to hold loan settings for a collateral type
    struct LoanSettings {
        uint256 loanCooldownPeriod;
        uint256 loanGracePeriod;
        uint256 maxLoanPoints;
        uint256 availableLoanPoints;
        uint256 loanRegenerationRate;
        uint256 lastLoanRegenerationTimestamp;
    }

    /// @notice Enum to represent the base rate type
    enum BaseRateType {
        Global,
        Local
    }

    /// @notice Structure to hold fee settings for a collateral type
    struct FeeSettings {
        uint256 redemptionsTimeoutFeePct;
        uint256 maxRedemptionsFeePct;
        uint256 minRedemptionsFeePct;
        uint256 minBorrowingFeePct;
        uint256 maxBorrowingFeePct;
        BaseRateType baseRateType;
    }

    /// @notice Structure to hold all settings for a collateral type
    struct Settings {
        uint256 debtCap;
        uint256 decommissionedOn;
        uint256 MCR;
        uint256 CCR;
        RedemptionSettings redemptionSettings;
        LoanSettings loanSettings;
        FeeSettings feeSettings;
    }

    /// @notice Structure to represent a collateral type and its associated contracts
    struct Collateral {
        uint8 version;
        IActivePool activePool;
        ICollateralSurplusPool collateralSurplusPool;
        IDefaultPool defaultPool;
        IERC20Metadata asset;
        IPriceFeed priceFeed;
        ISortedPositions sortedPositions;
        IPositionManager positionManager;
        bool sunset;
    }

    /// @notice Structure to represent a collateral type with its settings and associated contracts
    struct CollateralWithSettings {
        string name;
        string symbol;
        uint8 decimals;
        uint8 version;
        Settings settings;
        IActivePool activePool;
        ICollateralSurplusPool collateralSurplusPool;
        IDefaultPool defaultPool;
        IERC20Metadata asset;
        IPriceFeed priceFeed;
        ISortedPositions sortedPositions;
        IPositionManager positionManager;
        bool sunset;
        uint256 availableRedemptionPoints;
        uint256 availableLoanPoints;
    }

    /// @notice Adds support for a new collateral type
    /// @param collateralAddress Address of the collateral token
    /// @param positionManagerAddress Address of the PositionManager contract
    /// @param sortedPositionsAddress Address of the SortedPositions contract
    /// @param activePoolAddress Address of the ActivePool contract
    /// @param priceFeedAddress Address of the PriceFeed contract
    /// @param defaultPoolAddress Address of the DefaultPool contract
    /// @param collateralSurplusPoolAddress Address of the CollateralSurplusPool contract
    function supportCollateral(
        address collateralAddress,
        address positionManagerAddress,
        address sortedPositionsAddress,
        address activePoolAddress,
        address priceFeedAddress,
        address defaultPoolAddress,
        address collateralSurplusPoolAddress
    ) external;

    /// @notice Gets all active collateral types
    /// @return An array of Collateral structs representing active collateral types
    function getActiveCollaterals() external view returns (Collateral[] memory);

    /// @notice Gets the unique addresses of all active collateral tokens
    /// @return An array of addresses representing active collateral token addresses
    function getUniqueActiveCollateralAddresses() external view returns (address[] memory);

    /// @notice Gets the debt cap for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return debtCap The debt cap for the specified collateral type
    function getDebtCap(address asset, uint8 version) external view returns (uint debtCap);

    /// @notice Gets the Critical Collateral Ratio (CCR) for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return The CCR for the specified collateral type
    function getCCR(address asset, uint8 version) external view returns (uint);

    /// @notice Gets the Minimum Collateral Ratio (MCR) for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return The MCR for the specified collateral type
    function getMCR(address asset, uint8 version) external view returns (uint);

    /// @notice Gets the minimum borrowing fee percentage for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return The minimum borrowing fee percentage for the specified collateral type
    function getMinBorrowingFeePct(address asset, uint8 version) external view returns (uint);

    /// @notice Gets the maximum borrowing fee percentage for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return The maximum borrowing fee percentage for the specified collateral type
    function getMaxBorrowingFeePct(address asset, uint8 version) external view returns (uint);

    /// @notice Gets the minimum redemption fee percentage for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return The minimum redemption fee percentage for the specified collateral type
    function getMinRedemptionsFeePct(address asset, uint8 version) external view returns (uint);

    /// @notice Requires that the commissioning period has passed for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    function requireAfterCommissioningPeriod(address asset, uint8 version) external view;

    /// @notice Requires that a specific collateral type is active
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    function requireIsActive(address asset, uint8 version) external view;

    /// @notice Gets the Collateral struct for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return A Collateral struct representing the specified collateral type
    function getCollateralInstance(address asset, uint8 version) external view returns (ICollateralController.Collateral memory);

    /// @notice Gets the Settings struct for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return A Settings struct representing the settings for the specified collateral type
    function getSettings(address asset, uint8 version) external view returns (ICollateralController.Settings memory);

    /// @notice Gets the total collateral amount for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return assetColl The total collateral amount for the specified collateral type
    function getAssetColl(address asset, uint8 version) external view returns (uint assetColl);

    /// @notice Gets the total debt amount for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return assetDebt The total debt amount for the specified collateral type
    function getAssetDebt(address asset, uint8 version) external view returns (uint assetDebt);

    /// @notice Gets the version of a specific PositionManager
    /// @param positionManager Address of the PositionManager contract
    /// @return version The version of the specified PositionManager
    function getVersion(address positionManager) external view returns (uint8 version);

    /// @notice Checks if a specific collateral type is in Recovery Mode
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @param price Current price of the collateral
    /// @return A boolean indicating whether the collateral type is in Recovery Mode
    function checkRecoveryMode(address asset, uint8 version, uint price) external returns (bool);

    /// @notice Requires that there are no undercollateralized positions across all collateral types
    function requireNoUnderCollateralizedPositions() external;

    /// @notice Checks if a given address is a valid PositionManager
    /// @param positionManager Address to check
    /// @return A boolean indicating whether the address is a valid PositionManager
    function validPositionManager(address positionManager) external view returns (bool);

    /// @notice Checks if a specific collateral type is decommissioned
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return A boolean indicating whether the collateral type is decommissioned
    function isDecommissioned(address asset, uint8 version) external view returns (bool);

    /// @notice Checks if a specific PositionManager is decommissioned and its sunset period has elapsed
    /// @param pm Address of the PositionManager
    /// @param collateral Address of the collateral token
    /// @return A boolean indicating whether the PositionManager is decommissioned and its sunset period has elapsed
    function decommissionedAndSunsetPositionManager(address pm, address collateral) external view returns (bool);

    /// @notice Gets the base rate type (Global or Local)
    /// @return The base rate type
    function getBaseRateType() external view returns (BaseRateType);

    /// @notice Gets the timestamp of the last fee operation
    /// @return The timestamp of the last fee operation
    function getLastFeeOperationTime() external view returns (uint);

    /// @notice Gets the current base rate
    /// @return The current base rate
    function getBaseRate() external view returns (uint);

    /// @notice Decays the base rate from borrowing
    function decayBaseRateFromBorrowing() external;

    /// @notice Updates the timestamp of the last fee operation
    function updateLastFeeOpTime() external;

    /// @notice Calculates the number of minutes passed since the last fee operation
    /// @return The number of minutes passed since the last fee operation
    function minutesPassedSinceLastFeeOp() external view returns (uint);

    /// @notice Calculates the decayed base rate
    /// @return The decayed base rate
    function calcDecayedBaseRate() external view returns (uint);

    /// @notice Updates the base rate from redemption
    /// @param _CollateralDrawn Amount of collateral drawn
    /// @param _price Current price of the collateral
    /// @param _totalStableSupply Total supply of stable tokens
    /// @return The updated base rate
    function updateBaseRateFromRedemption(uint _CollateralDrawn, uint _price, uint _totalStableSupply) external returns (uint);

    /// @notice Regenerates and consumes redemption points
    /// @param amount Amount of redemption points to consume
    /// @return utilizationPCT The utilization percentage after consumption
    /// @return loadIncrease The increase in load after consumption
    function regenerateAndConsumeRedemptionPoints(uint amount) external returns (uint utilizationPCT, uint loadIncrease);

    /// @notice Gets the redemption cooldown requirement for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return escrowDuration The duration of the escrow period
    /// @return gracePeriod The grace period for redemptions
    /// @return redemptionsTimeoutFeePct The fee percentage for redemption timeouts
    function getRedemptionCooldownRequirement(address asset, uint8 version) external returns (uint escrowDuration,uint gracePeriod,uint redemptionsTimeoutFeePct);

    /// @notice Calculates the redemption points at a specific timestamp for a collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @param targetTimestamp The timestamp to calculate the redemption points for
    /// @return workingRedemptionPoints The redemption points at the specified timestamp
    function redemptionPointsAt(address asset, uint8 version, uint targetTimestamp) external view returns (uint workingRedemptionPoints);

    /// @notice Regenerates and consumes loan points
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @param amount Amount of loan points to consume
    /// @return utilizationPCT The utilization percentage after consumption
    /// @return loadIncrease The increase in load after consumption
    function regenerateAndConsumeLoanPoints(address asset, uint8 version, uint amount) external returns (uint utilizationPCT, uint loadIncrease);

    /// @notice Gets the loan cooldown requirement for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @return escrowDuration The duration of the escrow period
    /// @return gracePeriod The grace period for loans
    function getLoanCooldownRequirement(address asset, uint8 version) external view returns (uint escrowDuration, uint gracePeriod);

    /// @notice Calculates the loan points at a specific timestamp for a collateral type
    /// @param asset Address of the collateral token
    /// @param version Version of the collateral type
    /// @param targetTimestamp The timestamp to calculate the loan points for
    /// @return workingLoanPoints The loan points at the specified timestamp
    function loanPointsAt(address asset, uint8 version, uint targetTimestamp) external view returns (uint workingLoanPoints);

    /// @notice Calculates the borrowing rate for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param baseRate The base rate to use in the calculation
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The calculated borrowing rate
    function calcBorrowingRate(address asset, uint baseRate, uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Calculates the redemption rate for a specific collateral type
    /// @param asset Address of the collateral token
    /// @param baseRate The base rate to use in the calculation
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The calculated redemption rate
    function calcRedemptionRate(address asset, uint baseRate, uint suggestedAdditiveFeePCT) external view returns (uint);
}
ICollateralSurplusPool.sol 44 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "./ICanReceiveCollateral.sol";

/// @title ICollateralSurplusPool Interface
/// @notice Interface for the CollateralSurplusPool contract which manages surplus collateral
interface ICollateralSurplusPool is ICanReceiveCollateral {
    /// @notice Emitted when a user's collateral balance is updated
    /// @param _account The address of the account
    /// @param _newBalance The new balance of the account
    event CollBalanceUpdated(address indexed _account, uint _newBalance);

    /// @notice Emitted when collateral is sent to an account
    /// @param _to The address receiving the collateral
    /// @param _amount The amount of collateral sent
    event CollateralSent(address _to, uint _amount);

    /// @notice Sets the addresses of connected contracts
    /// @param _positionControllerAddress Address of the PositionController contract
    /// @param _positionManagerAddress Address of the PositionManager contract
    /// @param _activePoolAddress Address of the ActivePool contract
    /// @param _collateralAssetAddress Address of the collateral asset token
    function setAddresses(address _positionControllerAddress, address _positionManagerAddress, address _activePoolAddress, address _collateralAssetAddress) external;

    /// @notice Gets the total amount of collateral in the pool
    /// @return The total amount of collateral
    function getCollateral() external view returns (uint);

    /// @notice Gets the amount of claimable collateral for a specific account
    /// @param _account The address of the account
    /// @return The amount of claimable collateral for the account
    function getUserCollateral(address _account) external view returns (uint);

    /// @notice Accounts for surplus collateral for a specific account
    /// @param _account The address of the account
    /// @param _amount The amount of surplus collateral to account for
    function accountSurplus(address _account, uint _amount) external;

    /// @notice Allows an account to claim their surplus collateral
    /// @param _account The address of the account claiming the collateral
    function claimColl(address _account) external;
}
IDefaultPool.sol 28 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "./IPool.sol";
import "./ICanReceiveCollateral.sol";

/// @title IDefaultPool Interface
/// @notice Interface for the DefaultPool contract which manages defaulted debt and collateral
interface IDefaultPool is IPool, ICanReceiveCollateral {
    /// @notice Emitted when the STABLE debt in the DefaultPool is updated
    /// @param _STABLEDebt The new total STABLE debt amount
    event DefaultPoolSTABLEDebtUpdated(uint _STABLEDebt);

    /// @notice Emitted when the collateral balance in the DefaultPool is updated
    /// @param _Collateral The new total collateral amount
    event DefaultPoolCollateralBalanceUpdated(uint _Collateral);

    /// @notice Sends collateral from the DefaultPool to the ActivePool
    /// @param _amount The amount of collateral to send
    function sendCollateralToActivePool(uint _amount) external;

    /// @notice Sets the addresses of connected contracts
    /// @param _positionManagerAddress Address of the PositionManager contract
    /// @param _activePoolAddress Address of the ActivePool contract
    /// @param _collateralAssetAddress Address of the collateral asset token
    function setAddresses(address _positionManagerAddress, address _activePoolAddress, address _collateralAssetAddress) external;
}
IFeeToken.sol 33 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC2612.sol";

interface IFeeToken is IERC20 {
    /**
     * @dev Sends tokens directly to the Fee Staking contract
     * @param _sender The address of the token sender
     * @param _amount The amount of tokens to send
     */
    function sendToFeeStaking(address _sender, uint _amount) external;

    /**
     * @dev Mints new tokens
     * @param account The address that will receive the minted tokens
     * @param amount The amount of tokens to mint
     */
    function mint(address account, uint amount) external;

    /**
     * @dev Burns tokens
     * @param amount The amount of tokens to burn
     */
    function burn(uint amount) external;

    /**
     * @dev Returns the supply of the token which is mintable via the Minter
     * @return The base supply amount
     */
    function minterSupply() external view returns (uint);
}
IFeeTokenMinter.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

/**
 * @title IFeeTokenMinter
 * @dev Interface for the FeeTokenMinter contract, responsible for managing vesting entries on behalf of other system components
 */
interface IFeeTokenMinter {
    /**
     * @dev Appends a new vesting entry for an account
     * @param account The address of the account to receive the vested tokens
     * @param quantity The amount of tokens to be vested
     */
    function appendVestingEntry(address account, uint256 quantity) external;
}
IFeeTokenStaking.sol 121 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "./IBackstopPool.sol";

/**
 * @title IFeeTokenStaking
 * @dev Interface for the FeeTokenStaking contract.
 */
interface IFeeTokenStaking {
    /**
     * @dev Emitted when a user's stake amount changes
     * @param staker Address of the staker
     * @param newStake New stake amount
     */
    event StakeChanged(address indexed staker, uint newStake);

    /**
     * @dev Emitted when stable token rewards are withdrawn
     * @param staker Address of the staker
     * @param stableGain Amount of stable tokens withdrawn
     */
    event StakingStablesWithdrawn(address indexed staker, uint stableGain);

    /**
     * @dev Emitted when collateral rewards are withdrawn
     * @param asset Address of the collateral asset
     * @param staker Address of the staker
     * @param _amount Amount of collateral withdrawn
     */
    event StakingCollateralWithdrawn(address indexed asset, address indexed staker, uint _amount);

    /**
     * @dev Emitted when the cumulative collateral rewards per staked token is updated
     * @param asset Address of the collateral asset
     * @param _F_Collateral New cumulative collateral rewards value
     */
    event F_CollateralUpdated(address asset, uint _F_Collateral);

    /**
     * @dev Emitted when the cumulative stable token rewards per staked token is updated
     * @param _F_STABLE New cumulative stable token rewards value
     */
    event F_STABLEUpdated(uint _F_STABLE);

    /**
     * @dev Emitted when the total amount of staked FeeTokens is updated
     * @param _totalfeeTokenStaked New total amount of staked FeeTokens
     */
    event TotalFeeTokenStakedUpdated(uint _totalfeeTokenStaked);

    /**
     * @dev Emitted when a staker's reward snapshots are updated
     * @param _staker Address of the staker
     * @param _F_Collateral New collateral rewards snapshot
     * @param _F_STABLE New stable token rewards snapshot
     */
    event StakerSnapshotsUpdated(address _staker, uint _F_Collateral, uint _F_STABLE);

    /**
     * @dev Sets the addresses for the contract dependencies
     * @param _feeTokenAddress Address of the FeeToken contract
     * @param _stableTokenAddress Address of the StableToken contract
     * @param _positionControllerAddress Address of the PositionController contract
     * @param _collateralControllerAddress Address of the CollateralController contract
     */
    function setAddresses(
        address _feeTokenAddress,
        address _stableTokenAddress,
        address _positionControllerAddress,
        address _collateralControllerAddress
    ) external;

    /**
     * @dev Allows users to stake FeeTokens
     * @param _feeTokenAmount Amount of FeeTokens to stake
     */
    function stake(uint _feeTokenAmount) external;

    /**
     * @dev Allows users to unstake FeeTokens and claim rewards
     * @param _feeTokenAmount Amount of FeeTokens to unstake
     */
    function unstake(uint _feeTokenAmount) external;

    /**
     * @dev Increases the cumulative collateral rewards per staked token
     * @param asset Address of the collateral asset
     * @param version Version of the collateral asset
     * @param _CollateralFee Amount of collateral fee to distribute
     */
    function increaseF_Collateral(address asset, uint8 version, uint _CollateralFee) external;

    /**
     * @dev Increases the cumulative stable token rewards per staked token
     * @param _feeTokenFee Amount of stable token fee to distribute
     */
    function increaseF_STABLE(uint _feeTokenFee) external;

    /**
     * @dev Gets the pending collateral gains for a user
     * @param _user Address of the user
     * @return An array of CollateralGain structs representing pending gains
     */
    function getPendingCollateralGains(address _user) external view returns (IBackstopPool.CollateralGain[] memory);

    /**
     * @dev Gets the pending collateral gain for a specific asset and user
     * @param asset Address of the collateral asset
     * @param _user Address of the user
     * @return The pending collateral gain amount
     */
    function getPendingCollateralGain(address asset, address _user) external view returns (uint);

    /**
     * @dev Gets the pending stable token gain for a user
     * @param _user Address of the user
     * @return The pending stable token gain amount
     */
    function getPendingStableGain(address _user) external view returns (uint);
}
IPool.sol 28 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IPool Interface
/// @notice Interface for Pool contracts that manage collateral and stable debt
interface IPool {
    /// @notice Emitted when collateral is sent from the pool
    /// @param _to The address receiving the collateral
    /// @param _amount The amount of collateral sent
    event CollateralSent(address _to, uint _amount);

    /// @notice Gets the total amount of collateral in the pool
    /// @return The total amount of collateral
    function getCollateral() external view returns (uint);

    /// @notice Gets the total amount of stable debt in the pool
    /// @return The total amount of stable debt
    function getStableDebt() external view returns (uint);

    /// @notice Increases the stable debt in the pool
    /// @param _amount The amount to increase the debt by
    function increaseStableDebt(uint _amount) external;

    /// @notice Decreases the stable debt in the pool
    /// @param _amount The amount to decrease the debt by
    function decreaseStableDebt(uint _amount) external;
}
IPositionController.sol 128 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IPositionController Interface
/// @notice Interface for the PositionController contract which manages user positions
interface IPositionController {
    /// @notice Emitted when a new position is created
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _borrower The address of the position owner
    /// @param arrayIndex The index of the position in the positions array
    event PositionCreated(address indexed asset, uint8 indexed version, address indexed _borrower, uint arrayIndex);

    /// @notice Emitted when a position is updated
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _borrower The address of the position owner
    /// @param _debt The new debt amount of the position
    /// @param _coll The new collateral amount of the position
    /// @param stake The new stake amount of the position
    /// @param operation The type of operation performed (e.g., open, close, adjust)
    event PositionUpdated(address indexed asset, uint8 indexed version, address indexed _borrower, uint _debt, uint _coll, uint stake, uint8 operation);

    /// @notice Emitted when a borrowing fee is paid
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _borrower The address of the position owner
    /// @param _stableFee The amount of fee paid in stable tokens
    event StableBorrowingFeePaid(address indexed asset, uint8 indexed version, address indexed _borrower, uint _stableFee);

    /// @notice Sets the addresses of connected contracts
    /// @param _collateralController Address of the CollateralController contract
    /// @param _backstopPoolAddress Address of the BackstopPool contract
    /// @param _gasPoolAddress Address of the GasPool contract
    /// @param _stableTokenAddress Address of the StableToken contract
    /// @param _feeTokenStakingAddress Address of the FeeTokenStaking contract
    function setAddresses(
        address _collateralController,
        address _backstopPoolAddress,
        address _gasPoolAddress,
        address _stableTokenAddress,
        address _feeTokenStakingAddress
    ) external;

    /// @notice Opens a new position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param suppliedCollateral The amount of collateral supplied
    /// @param _maxFee The maximum fee percentage the user is willing to pay
    /// @param _stableAmount The amount of stable tokens to borrow
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function openPosition(address asset, uint8 version, uint suppliedCollateral, uint _maxFee, uint _stableAmount,
        address _upperHint, address _lowerHint) external;

    /// @notice Adds collateral to an existing position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _collAddition The amount of collateral to add
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function addColl(address asset, uint8 version, uint _collAddition, address _upperHint, address _lowerHint) external;

    /// @notice Moves collateral gain to a position (called by BackstopPool)
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _collAddition The amount of collateral to add
    /// @param _user The address of the position owner
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function moveCollateralGainToPosition(address asset, uint8 version, uint _collAddition, address _user,
        address _upperHint, address _lowerHint) external;

    /// @notice Withdraws collateral from a position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _amount The amount of collateral to withdraw
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function withdrawColl(address asset, uint8 version, uint _amount, address _upperHint, address _lowerHint) external;

    /// @notice Withdraws stable tokens from a position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _maxFee The maximum fee percentage the user is willing to pay
    /// @param _amount The amount of stable tokens to withdraw
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function withdrawStable(address asset, uint8 version, uint _maxFee, uint _amount,
        address _upperHint, address _lowerHint) external;

    /// @notice Repays stable tokens to a position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _amount The amount of stable tokens to repay
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function repayStable(address asset, uint8 version, uint _amount, address _upperHint, address _lowerHint) external;

    /// @notice Closes a position
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    function closePosition(address asset, uint8 version) external;

    /// @notice Adjusts a position's collateral and debt
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    /// @param _collAddition The amount of collateral to add
    /// @param _maxFee The maximum fee percentage the user is willing to pay
    /// @param _collWithdrawal The amount of collateral to withdraw
    /// @param _debtChange The amount of debt to change
    /// @param isDebtIncrease True if the debt is increasing, false if decreasing
    /// @param _upperHint The address hint for position insertion (upper bound)
    /// @param _lowerHint The address hint for position insertion (lower bound)
    function adjustPosition(address asset, uint8 version, uint _collAddition, uint _maxFee,
        uint _collWithdrawal, uint _debtChange, bool isDebtIncrease, address _upperHint, address _lowerHint) external;

    /// @notice Claims any remaining collateral after position closure
    /// @param asset The address of the collateral asset
    /// @param version The version of the collateral
    function claimCollateral(address asset, uint8 version) external;

    /// @notice Calculates the composite debt (debt + gas compensation)
    /// @param _debt The base debt amount
    /// @return The composite debt amount
    function getCompositeDebt(uint _debt) external view returns (uint);
}
IPositionManager.sol 254 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IPositionManager Interface
/// @notice Interface for the PositionManager contract which manages individual positions
interface IPositionManager {
    /// @notice Emitted when a redemption occurs
    /// @param _attemptedStableAmount The amount of stable tokens attempted to redeem
    /// @param _actualStableAmount The actual amount of stable tokens redeemed
    /// @param _CollateralSent The amount of collateral sent to the redeemer
    /// @param _CollateralFee The fee paid in collateral for the redemption
    event Redemption(uint _attemptedStableAmount, uint _actualStableAmount, uint _CollateralSent, uint _CollateralFee);

    /// @notice Emitted when total stakes are updated
    /// @param _newTotalStakes The new total stakes value
    event TotalStakesUpdated(uint _newTotalStakes);

    /// @notice Emitted when system snapshots are updated
    /// @param _totalStakesSnapshot The new total stakes snapshot
    /// @param _totalCollateralSnapshot The new total collateral snapshot
    event SystemSnapshotsUpdated(uint _totalStakesSnapshot, uint _totalCollateralSnapshot);

    /// @notice Emitted when L terms are updated
    /// @param _L_Collateral The new L_Collateral value
    /// @param _L_STABLE The new L_STABLE value
    event LTermsUpdated(uint _L_Collateral, uint _L_STABLE);

    /// @notice Emitted when position snapshots are updated
    /// @param _L_Collateral The new L_Collateral value for the position
    /// @param _L_STABLEDebt The new L_STABLEDebt value for the position
    event PositionSnapshotsUpdated(uint _L_Collateral, uint _L_STABLEDebt);

    /// @notice Emitted when a position's index is updated
    /// @param _borrower The address of the position owner
    /// @param _newIndex The new index value
    event PositionIndexUpdated(address _borrower, uint _newIndex);

    /// @notice Get the total count of position owners
    /// @return The number of position owners
    function getPositionOwnersCount() external view returns (uint);

    /// @notice Get a position owner's address by index
    /// @param _index The index in the position owners array
    /// @return The address of the position owner
    function getPositionFromPositionOwnersArray(uint _index) external view returns (address);

    /// @notice Get the nominal ICR (Individual Collateral Ratio) of a position
    /// @param _borrower The address of the position owner
    /// @return The nominal ICR of the position
    function getNominalICR(address _borrower) external view returns (uint);

    /// @notice Get the current ICR of a position
    /// @param _borrower The address of the position owner
    /// @param _price The current price of the collateral
    /// @return The current ICR of the position
    function getCurrentICR(address _borrower, uint _price) external view returns (uint);

    /// @notice Liquidate a single position
    /// @param _borrower The address of the position owner to liquidate
    function liquidate(address _borrower) external;

    /// @notice Liquidate multiple positions
    /// @param _n The number of positions to attempt to liquidate
    function liquidatePositions(uint _n) external;

    /// @notice Batch liquidate a specific set of positions
    /// @param _positionArray An array of position owner addresses to liquidate
    function batchLiquidatePositions(address[] calldata _positionArray) external;

    /// @notice Queue a redemption request
    /// @param _stableAmount The amount of stable tokens to queue for redemption
    function queueRedemption(uint _stableAmount) external;

    /// @notice Redeem collateral for stable tokens
    /// @param _stableAmount The amount of stable tokens to redeem
    /// @param _firstRedemptionHint The address of the first position to consider for redemption
    /// @param _upperPartialRedemptionHint The address of the position just above the partial redemption
    /// @param _lowerPartialRedemptionHint The address of the position just below the partial redemption
    /// @param _partialRedemptionHintNICR The nominal ICR of the partial redemption hint
    /// @param _maxIterations The maximum number of iterations to perform in the redemption algorithm
    /// @param _maxFee The maximum acceptable fee percentage for the redemption
    function redeemCollateral(
        uint _stableAmount,
        address _firstRedemptionHint,
        address _upperPartialRedemptionHint,
        address _lowerPartialRedemptionHint,
        uint _partialRedemptionHintNICR,
        uint _maxIterations,
        uint _maxFee
    ) external;

    /// @notice Update the stake and total stakes for a position
    /// @param _borrower The address of the position owner
    /// @return The new stake value
    function updateStakeAndTotalStakes(address _borrower) external returns (uint);

    /// @notice Update the reward snapshots for a position
    /// @param _borrower The address of the position owner
    function updatePositionRewardSnapshots(address _borrower) external;

    /// @notice Add a position owner to the array of position owners
    /// @param _borrower The address of the position owner
    /// @return index The index of the new position owner in the array
    function addPositionOwnerToArray(address _borrower) external returns (uint index);

    /// @notice Apply pending rewards to a position
    /// @param _borrower The address of the position owner
    function applyPendingRewards(address _borrower) external;

    /// @notice Get the pending collateral reward for a position
    /// @param _borrower The address of the position owner
    /// @return The amount of pending collateral reward
    function getPendingCollateralReward(address _borrower) external view returns (uint);

    /// @notice Get the pending stable debt reward for a position
    /// @param _borrower The address of the position owner
    /// @return The amount of pending stable debt reward
    function getPendingStableDebtReward(address _borrower) external view returns (uint);

    /// @notice Check if a position has pending rewards
    /// @param _borrower The address of the position owner
    /// @return True if the position has pending rewards, false otherwise
    function hasPendingRewards(address _borrower) external view returns (bool);

    /// @notice Get the entire debt and collateral for a position, including pending rewards
    /// @param _borrower The address of the position owner
    /// @return debt The total debt of the position
    /// @return coll The total collateral of the position
    /// @return pendingStableDebtReward The pending stable debt reward
    /// @return pendingCollateralReward The pending collateral reward
    function getEntireDebtAndColl(address _borrower)
    external view returns (uint debt, uint coll, uint pendingStableDebtReward, uint pendingCollateralReward);

    /// @notice Close a position
    /// @param _borrower The address of the position owner
    function closePosition(address _borrower) external;

    /// @notice Remove the stake for a position
    /// @param _borrower The address of the position owner
    function removeStake(address _borrower) external;

    /// @notice Get the current redemption rate
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The current redemption rate
    function getRedemptionRate(uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the redemption rate with decay
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The redemption rate with decay applied
    function getRedemptionRateWithDecay(uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the redemption fee with decay
    /// @param _CollateralDrawn The amount of collateral drawn
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The redemption fee with decay applied
    function getRedemptionFeeWithDecay(uint _CollateralDrawn, uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the current borrowing rate
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The current borrowing rate
    function getBorrowingRate(uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the borrowing rate with decay
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The borrowing rate with decay applied
    function getBorrowingRateWithDecay(uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the borrowing fee
    /// @param stableDebt The amount of stable debt
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The borrowing fee
    function getBorrowingFee(uint stableDebt, uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Get the borrowing fee with decay
    /// @param _stableDebt The amount of stable debt
    /// @param suggestedAdditiveFeePCT The suggested additive fee percentage
    /// @return The borrowing fee with decay applied
    function getBorrowingFeeWithDecay(uint _stableDebt, uint suggestedAdditiveFeePCT) external view returns (uint);

    /// @notice Decay the base rate from borrowing
    function decayBaseRateFromBorrowing() external;

    /// @notice Get the status of a position
    /// @param _borrower The address of the position owner
    /// @return The status of the position
    function getPositionStatus(address _borrower) external view returns (uint);

    /// @notice Get the stake of a position
    /// @param _borrower The address of the position owner
    /// @return The stake of the position
    function getPositionStake(address _borrower) external view returns (uint);

    /// @notice Get the debt of a position
    /// @param _borrower The address of the position owner
    /// @return The debt of the position
    function getPositionDebt(address _borrower) external view returns (uint);

    /// @notice Get the collateral of a position
    /// @param _borrower The address of the position owner
    /// @return The collateral of the position
    function getPositionColl(address _borrower) external view returns (uint);

    /// @notice Set the status of a position
    /// @param _borrower The address of the position owner
    /// @param num The new status value
    function setPositionStatus(address _borrower, uint num) external;

    /// @notice Increase the collateral of a position
    /// @param _borrower The address of the position owner
    /// @param _collIncrease The amount of collateral to increase
    /// @return The new collateral amount
    function increasePositionColl(address _borrower, uint _collIncrease) external returns (uint);

    /// @notice Decrease the collateral of a position
    /// @param _borrower The address of the position owner
    /// @param _collDecrease The amount of collateral to decrease
    /// @return The new collateral amount
    function decreasePositionColl(address _borrower, uint _collDecrease) external returns (uint);

    /// @notice Increase the debt of a position
    /// @param _borrower The address of the position owner
    /// @param _debtIncrease The amount of debt to increase
    /// @return The new debt amount
    function increasePositionDebt(address _borrower, uint _debtIncrease) external returns (uint);

    /// @notice Decrease the debt of a position
    /// @param _borrower The address of the position owner
    /// @param _debtDecrease The amount of debt to decrease
    /// @return The new debt amount
    function decreasePositionDebt(address _borrower, uint _debtDecrease) external returns (uint);

    /// @notice Get the entire debt of the system
    /// @return total The total debt in the system
    function getEntireDebt() external view returns (uint total);

    /// @notice Get the entire collateral in the system
    /// @return total The total collateral in the system
    function getEntireCollateral() external view returns (uint total);

    /// @notice Get the Total Collateral Ratio (TCR) of the system
    /// @param _price The current price of the collateral
    /// @return TCR The Total Collateral Ratio
    function getTCR(uint _price) external view returns(uint TCR);

    /// @notice Check if the system is in Recovery Mode
    /// @param _price The current price of the collateral
    /// @return True if the system is in Recovery Mode, false otherwise
    function checkRecoveryMode(uint _price) external returns(bool);

    /// @notice Check if the position manager is in sunset mode
    /// @return True if the position manager is in sunset mode, false otherwise
    function isSunset() external returns(bool);
}
IPriceFeed.sol 67 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IPriceFeed Interface
/// @notice Interface for price feed contracts that provide various price-related functionalities
interface IPriceFeed {
    /// @notice Enum to represent the current operational mode of the oracle
    enum OracleMode {AUTOMATED, FALLBACK}

    /// @notice Struct to hold detailed price information
    struct PriceDetails {
        uint lowestPrice;
        uint highestPrice;
        uint weightedAveragePrice;
        uint spotPrice;
        uint shortTwapPrice;
        uint longTwapPrice;
        uint suggestedAdditiveFeePCT;
        OracleMode currentMode;
    }

    /// @notice Fetches the current price details
    /// @param utilizationPCT The current utilization percentage
    /// @return A PriceDetails struct containing various price metrics
    function fetchPrice(uint utilizationPCT) external view returns (PriceDetails memory);

    /// @notice Fetches the weighted average price, used during liquidations
    /// @param testLiquidity Whether to test for liquidity
    /// @param testDeviation Whether to test for price deviation
    /// @return price The weighted average price
    function fetchWeightedAveragePrice(bool testLiquidity, bool testDeviation) external returns (uint price);

    /// @notice Fetches the lowest price, used when exiting escrow or testing for under-collateralized positions
    /// @param testLiquidity Whether to test for liquidity
    /// @param testDeviation Whether to test for price deviation
    /// @return price The lowest price
    function fetchLowestPrice(bool testLiquidity, bool testDeviation) external returns (uint price);

    /// @notice Fetches the lowest price with a fee suggestion, used when issuing new debt
    /// @param loadIncrease The increase in load
    /// @param originationOrRedemptionLoadPCT The origination or redemption load percentage
    /// @param testLiquidity Whether to test for liquidity
    /// @param testDeviation Whether to test for price deviation
    /// @return price The lowest price
    /// @return suggestedAdditiveFeePCT The suggested additive fee percentage
    function fetchLowestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external returns (uint price, uint suggestedAdditiveFeePCT);

    /// @notice Fetches the highest price with a fee suggestion, used during redemptions
    /// @param loadIncrease The increase in load
    /// @param originationOrRedemptionLoadPCT The origination or redemption load percentage
    /// @param testLiquidity Whether to test for liquidity
    /// @param testDeviation Whether to test for price deviation
    /// @return price The highest price
    /// @return suggestedAdditiveFeePCT The suggested additive fee percentage
    function fetchHighestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external returns (uint price, uint suggestedAdditiveFeePCT);
}
IRecoverable.sol 13 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title IRecoverable Interface
/// @notice Interface for contracts that can recover orphaned tokens
interface IRecoverable {
    /// @notice Extracts orphaned tokens from the contract
    /// @dev This function should only be callable by authorized roles (e.g., guardian)
    /// @param asset The address of the token to be extracted
    /// @param version The version of the token (if applicable)
    function extractOrphanedTokens(address asset, uint8 version) external;
}
ISortedPositions.sol 102 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/// @title ISortedPositions Interface
/// @notice Interface for a sorted list of positions, ordered by their Individual Collateral Ratio (ICR)
interface ISortedPositions {
    /// @notice Emitted when the PositionManager address is changed
    /// @param _positionManagerAddress The new address of the PositionManager
    event PositionManagerAddressChanged(address _positionManagerAddress);

    /// @notice Emitted when the PositionController address is changed
    /// @param _positionControllerAddress The new address of the PositionController
    event PositionControllerAddressChanged(address _positionControllerAddress);

    /// @notice Emitted when a new node (position) is added to the list
    /// @param _id The address of the new position
    /// @param _NICR The Nominal Individual Collateral Ratio of the new position
    event NodeAdded(address _id, uint _NICR);

    /// @notice Emitted when a node (position) is removed from the list
    /// @param _id The address of the removed position
    event NodeRemoved(address _id);

    /// @notice Sets the parameters for the sorted list
    /// @param _size The maximum size of the list
    /// @param _positionManagerAddress The address of the PositionManager contract
    /// @param _positionControllerAddress The address of the PositionController contract
    function setParams(uint256 _size, address _positionManagerAddress, address _positionControllerAddress) external;

    /// @notice Inserts a new node (position) into the list
    /// @param _id The address of the new position
    /// @param _ICR The Individual Collateral Ratio of the new position
    /// @param _prevId The address of the previous node in the insertion position
    /// @param _nextId The address of the next node in the insertion position
    function insert(address _id, uint256 _ICR, address _prevId, address _nextId) external;

    /// @notice Removes a node (position) from the list
    /// @param _id The address of the position to remove
    function remove(address _id) external;

    /// @notice Re-inserts a node (position) into the list with a new ICR
    /// @param _id The address of the position to re-insert
    /// @param _newICR The new Individual Collateral Ratio of the position
    /// @param _prevId The address of the previous node in the new insertion position
    /// @param _nextId The address of the next node in the new insertion position
    function reInsert(address _id, uint256 _newICR, address _prevId, address _nextId) external;

    /// @notice Checks if a position is in the list
    /// @param _id The address of the position to check
    /// @return bool True if the position is in the list, false otherwise
    function contains(address _id) external view returns (bool);

    /// @notice Checks if the list is full
    /// @return bool True if the list is full, false otherwise
    function isFull() external view returns (bool);

    /// @notice Checks if the list is empty
    /// @return bool True if the list is empty, false otherwise
    function isEmpty() external view returns (bool);

    /// @notice Gets the current size of the list
    /// @return uint256 The current number of positions in the list
    function getSize() external view returns (uint256);

    /// @notice Gets the maximum size of the list
    /// @return uint256 The maximum number of positions the list can hold
    function getMaxSize() external view returns (uint256);

    /// @notice Gets the first position in the list (highest ICR)
    /// @return address The address of the first position
    function getFirst() external view returns (address);

    /// @notice Gets the last position in the list (lowest ICR)
    /// @return address The address of the last position
    function getLast() external view returns (address);

    /// @notice Gets the next position in the list after a given position
    /// @param _id The address of the current position
    /// @return address The address of the next position
    function getNext(address _id) external view returns (address);

    /// @notice Gets the previous position in the list before a given position
    /// @param _id The address of the current position
    /// @return address The address of the previous position
    function getPrev(address _id) external view returns (address);

    /// @notice Checks if a given insertion position is valid for a new ICR
    /// @param _ICR The ICR of the position to insert
    /// @param _prevId The address of the proposed previous node
    /// @param _nextId The address of the proposed next node
    /// @return bool True if the insertion position is valid, false otherwise
    function validInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (bool);

    /// @notice Finds the correct insertion position for a given ICR
    /// @param _ICR The ICR of the position to insert
    /// @param _prevId A hint for the previous node
    /// @param _nextId A hint for the next node
    /// @return address The address of the previous node for insertion
    /// @return address The address of the next node for insertion
    function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address);
}
IStable.sol 38 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC2612.sol";

/// @title IStable Interface
/// @notice Interface for the Stable token contract, extending ERC20 and ERC2612 functionality
interface IStable is IERC20, IERC2612 {
    /// @notice Mints new tokens to a specified account
    /// @param _account The address to receive the minted tokens
    /// @param _amount The amount of tokens to mint
    function mint(address _account, uint256 _amount) external;

    /// @notice Burns tokens from a specified account
    /// @param _account The address from which to burn tokens
    /// @param _amount The amount of tokens to burn
    function burn(address _account, uint256 _amount) external;

    /// @notice Transfers tokens from a sender to a pool
    /// @param _sender The address sending the tokens
    /// @param poolAddress The address of the pool receiving the tokens
    /// @param _amount The amount of tokens to transfer
    function sendToPool(address _sender, address poolAddress, uint256 _amount) external;

    /// @notice Transfers tokens for redemption escrow
    /// @param from The address sending the tokens
    /// @param to The address receiving the tokens (likely a position manager)
    /// @param amount The amount of tokens to transfer
    function transferForRedemptionEscrow(address from, address to, uint amount) external;

    /// @notice Returns tokens from a pool to a user
    /// @param poolAddress The address of the pool sending the tokens
    /// @param user The address of the user receiving the tokens
    /// @param _amount The amount of tokens to return
    function returnFromPool(address poolAddress, address user, uint256 _amount) external;
}
ORX.sol 146 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/IFeeToken.sol";

/**
 * @title ORX Fee Sharing Token Contract
 * @dev Implementation of the ORX token using OpenZeppelin contracts.
 * This contract includes minting, burning, and staking functionalities.
 */
contract ORX is IFeeToken, ERC20, ERC20Permit, Ownable {
    /// @dev The initial liquidity pool share of ORX tokens.
    uint256 private constant INITIAL_LP_SHARE = 600_000e18;

    /// @dev The referral program share of ORX tokens which may or may not be availed of.
    uint256 private constant REF_SHARE = 20_000_000e18;

    /// @dev The backstop pool incentives share of ORX tokens.
    uint256 private constant BACKSTOP_INCENTIVES_SHARE = 200_000_000e18;

    /// @dev The USDX-WETH V2 liquidity pool share of ORX tokens.
    uint256 private constant USDX_WETH_LP_SHARE = 50_000_000e18;

    /// @dev The base supply of ORX tokens.
    uint256 public constant BASE_SUPPLY = 1_000_000_000e18;

    /**
     * @dev The maximum total supply cap for ORX tokens.
     * This value is set to 1,350,600,000 ORX
     * The supply cap is composed of:
     * - INITIAL_LP_SHARE: 600,000 ORX
     * - REF_SHARE: 20,000,000 ORX
     * - BASE_SUPPLY: 1,000,000,000 ORX
     * - INCENTIVES_SHARE: 200,000,000 ORX
     * - USDX_WETH_LP_SHARE: 50,000,000 ORX
     * Total: 1,270,600,000 ORX
     */
    uint256 public constant SUPPLY_CAP =
        INITIAL_LP_SHARE + REF_SHARE + BASE_SUPPLY + BACKSTOP_INCENTIVES_SHARE + USDX_WETH_LP_SHARE;

    /// @dev The address of the ORX fee sharing contract.
    address public immutable orxStakingAddress;

    /**
     * @dev The address of the FeeTokenMinter contract.
     * This address is set once by the contract owner, followed by an immediate revocation of ownership.
     */
    address public minterAddress;

    /**
     * @dev Constructor to initialize the ORX token.
     * @param _orxStakingAddress The address of the ORX staking contract.
     */
    constructor(address _orxStakingAddress)
    ERC20("Ouroboros", "ORX")
    ERC20Permit("Ouroboros")
    {
        require(_orxStakingAddress != address(0), "_orxStakingAddress is the null address");

        orxStakingAddress = _orxStakingAddress;
    }

    /**
     * @dev Sets the minter address. Can only be called once by the mintSetter.
     * @param _minterAddress The address to be set as the minter.
     */
    function attachMinter(address _minterAddress) external onlyOwner {
        require(_minterAddress != address(0), "_minterAddress is the null address");

        minterAddress = _minterAddress;
        renounceOwnership();
    }

    /**
     * @dev Mints new tokens. Can only be called by the minter address.
     * @param account The address that will receive the minted tokens.
     * @param amount The amount of tokens to mint.
     */
    function mint(address account, uint256 amount) external virtual {
        require(msg.sender == minterAddress, "ORX: caller must be the Minter contract");
        require((totalSupply() + amount) <= SUPPLY_CAP, "Mint exceeds hard supply cap");
        _mint(account, amount);
    }

    /**
     * @dev Burns tokens from sender account.
     * @param amount The amount of tokens to burn.
     */
    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }

    /**
     * @dev Sends tokens to the ORX staking contract. Can only be called by the staking contract.
     * @param _sender The address sending the tokens.
     * @param _amount The amount of tokens to send.
     */
    function sendToFeeStaking(address _sender, uint256 _amount) external {
        require(msg.sender == orxStakingAddress, "ORX: caller must be the OrxStaking contract");
        _transfer(_sender, orxStakingAddress, _amount);
    }

    /**
     * @dev Overrides the transfer function to include recipient validation.
     * @param recipient The address to receive the tokens.
     * @param amount The amount of tokens to transfer.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transfer(address recipient, uint256 amount) public override(IERC20, ERC20) returns (bool) {
        _requireValidRecipient(recipient);
        return super.transfer(recipient, amount);
    }

    /**
     * @dev Overrides the transferFrom function to include recipient validation.
     * @param sender The address sending the tokens.
     * @param recipient The address to receive the tokens.
     * @param amount The amount of tokens to transfer.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public override(IERC20, ERC20) returns (bool) {
        _requireValidRecipient(recipient);
        return super.transferFrom(sender, recipient, amount);
    }

    /**
     * @dev Returns the supply of the token which is mintable via the Minter
     * @return The base supply amount
     */
    function minterSupply() external override pure returns (uint) {
        return BASE_SUPPLY;
    }

    /**
     * @dev Validates the recipient address.
     * @param _recipient The address to validate.
     */
    function _requireValidRecipient(address _recipient) internal view {
        require(_recipient != address(this), "ORX Cannot transfer tokens directly to the ORX token contract");
        require(_recipient != orxStakingAddress, "ORX Cannot transfer tokens directly to the staking contract");
    }
}
FeeTokenStaking.sol 370 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/IFeeTokenStaking.sol";
import "../../Guardable.sol";
import "../../interfaces/IRecoverable.sol";
import "../../interfaces/IStable.sol";
import "../../interfaces/ICollateralController.sol";
import "../../interfaces/IFeeToken.sol";
import "../../common/StableMath.sol";
import "../../common/BaseMath.sol";

/**
 * @title FeeTokenStaking
 * @dev Contract for staking FeeTokens and earning rewards in collateral and stable tokens.
 *
 * Key features:
 *      1. Staking: Users can stake FeeTokens to earn rewards.
 *      2. Reward Distribution: Distributes rewards in multiple collateral types and stable tokens.
 *      3. Recovery: Unclaimed rewards, or accidentally sent tokens can be recovered following the decommissioning period.
 */
contract FeeTokenStaking is IFeeTokenStaking, Ownable, Guardable, BaseMath, IRecoverable {
    //==================================================================//
    //-------------------------- INTERFACES ----------------------------//
    //==================================================================//

    IFeeToken public feeToken;
    IStable public stableToken;
    ICollateralController public collateralController;
    address public positionControllerAddress;

    //==================================================================//
    //----------------------- STATE VARIABLES --------------------------//
    //==================================================================//

    /// @dev Total amount of FeeTokens staked
    uint public totalFeeTokenStaked;

    /// @dev Mapping of user addresses to their staked FeeToken amounts
    mapping(address => uint) public feeTokenStakes;

    /// @dev Mapping of collateral addresses to their cumulative rewards per staked token
    mapping(address => uint) public F_Collateral;

    /// @dev Cumulative stable token rewards per staked token
    uint public F_STABLE;

    /// @dev Mapping of user addresses to their reward snapshots
    mapping(address => Snapshot) public snapshots;

    //==================================================================//
    //--------------------------- STRUCTS ------------------------------//
    //==================================================================//

    /// @dev Structure to hold user reward snapshots
    struct Snapshot {
        mapping(address => uint) F_Collateral_Snapshot;
        uint F_STABLE_Snapshot;
    }

    //==================================================================//
    //----------------------- SETUP FUNCTIONS --------------------------//
    //==================================================================//

    /**
     * @dev Sets the addresses for the contract dependencies
     * @param _feeTokenAddress Address of the FeeToken contract
     * @param _stableTokenAddress Address of the StableToken contract
     * @param _positionControllerAddress Address of the PositionController contract
     * @param _collateralControllerAddress Address of the CollateralController contract
     */
    function setAddresses(
        address _feeTokenAddress,
        address _stableTokenAddress,
        address _positionControllerAddress,
        address _collateralControllerAddress
    ) external onlyOwner override {
        require(_feeTokenAddress != address(0), "_feeTokenAddress is the null address");
        require(_stableTokenAddress != address(0), "_stableTokenAddress is the null address");
        require(_positionControllerAddress != address(0), "_positionControllerAddress is the null address");
        require(_collateralControllerAddress != address(0), "_collateralControllerAddress is the null address");

        feeToken = IFeeToken(_feeTokenAddress);
        stableToken = IStable(_stableTokenAddress);
        positionControllerAddress = _positionControllerAddress;
        collateralController = ICollateralController(_collateralControllerAddress);
        renounceOwnership();
    }

    //==================================================================//
    //----------------- EXTERNAL MUTATIVE FUNCTIONS --------------------//
    //==================================================================//

    /**
     * @dev Allows users to stake FeeTokens
     * @param _feeTokenAmount Amount of FeeTokens to stake
     */
    function stake(uint _feeTokenAmount) external override {
        _requireNonZeroAmount(_feeTokenAmount);
        uint currentStake = feeTokenStakes[msg.sender];
        IBackstopPool.CollateralGain[] memory collateralGains;

        uint STABLEGain;
        if (currentStake != 0) {
            collateralGains = _getPendingCollateralGains(msg.sender);
            STABLEGain = _getPendingStableGain(msg.sender);
        }

        _updateUserSnapshots(msg.sender);

        uint newStake = currentStake + _feeTokenAmount;

        feeTokenStakes[msg.sender] = newStake;
        totalFeeTokenStaked = totalFeeTokenStaked + _feeTokenAmount;
        emit TotalFeeTokenStakedUpdated(totalFeeTokenStaked);

        feeToken.sendToFeeStaking(msg.sender, _feeTokenAmount);
        emit StakeChanged(msg.sender, newStake);
        emit StakingStablesWithdrawn(msg.sender, STABLEGain);

        if (currentStake != 0) {
            if(STABLEGain > 0) {
                stableToken.transfer(msg.sender, STABLEGain);
            }
            _sendCollateralGainsToUser(collateralGains);
        }
    }

    /**
     * @dev Allows users to unstake FeeTokens and claim rewards
     * @param _feeTokenAmount Amount of FeeTokens to unstake
     */
    function unstake(uint _feeTokenAmount) external override {
        uint currentStake = feeTokenStakes[msg.sender];
        _requireUserHasStake(currentStake);

        IBackstopPool.CollateralGain[] memory collateralGains = _getPendingCollateralGains(msg.sender);
        uint stableGain = _getPendingStableGain(msg.sender);

        _updateUserSnapshots(msg.sender);

        if (_feeTokenAmount > 0) {
            uint feeTokensToWithdraw = StableMath._min(_feeTokenAmount, currentStake);
            uint newStake = currentStake - feeTokensToWithdraw;

            feeTokenStakes[msg.sender] = newStake;
            totalFeeTokenStaked = totalFeeTokenStaked - feeTokensToWithdraw;
            emit TotalFeeTokenStakedUpdated(totalFeeTokenStaked);

            feeToken.transfer(msg.sender, feeTokensToWithdraw);
            emit StakeChanged(msg.sender, newStake);
        }

        emit StakingStablesWithdrawn(msg.sender, stableGain);

        if(stableGain > 0) {
            stableToken.transfer(msg.sender, stableGain);
        }

        _sendCollateralGainsToUser(collateralGains);
    }

    /**
     * @dev Increases the cumulative collateral rewards per staked token
     * @param asset Address of the collateral asset
     * @param version Version of the collateral asset
     * @param _CollateralFee Amount of collateral fee to distribute
     */
    function increaseF_Collateral(address asset, uint8 version, uint _CollateralFee) external override {
        _requireCallerIsPositionManager(asset, version);
        uint CollateralFeePerFeeTokenStaked;

        if (totalFeeTokenStaked > 0) {
            CollateralFeePerFeeTokenStaked = (_CollateralFee * DECIMAL_PRECISION) / totalFeeTokenStaked;
        }

        F_Collateral[asset] = F_Collateral[asset] + CollateralFeePerFeeTokenStaked;
        emit F_CollateralUpdated(asset, F_Collateral[asset]);
    }

    /**
     * @dev Increases the cumulative stable token rewards per staked token
     * @param _STABLEFee Amount of stable token fee to distribute
     */
    function increaseF_STABLE(uint _STABLEFee) external override {
        _requireCallerIsPCorPM();
        uint StableFeePerFeeTokenStaked;

        if (totalFeeTokenStaked > 0) {
            StableFeePerFeeTokenStaked = (_STABLEFee * DECIMAL_PRECISION) / totalFeeTokenStaked;
        }

        F_STABLE = F_STABLE + StableFeePerFeeTokenStaked;
        emit F_STABLEUpdated(F_STABLE);
    }

    /**
     * @dev Allows the guardian to extract orphaned tokens from the contract
     * @param asset Address of the token to extract
     * @param version Version of the token
     */
    function extractOrphanedTokens(address asset, uint8 version) external override onlyGuardian {
        require(asset != address(stableToken), "Naughty...");

        ICollateralController.Collateral[] memory collaterals = collateralController.getActiveCollaterals();
        for (uint idx; idx < collaterals.length; idx++) {
            // Should not be able to extract tokens in the contract which are under normal operation.
            // Only tokens which are not claimed by users before sunset can be extracted,
            // or tokens which are accidentally sent to the contract.
            require(address(collaterals[idx].asset) != asset, "Guardian can only extract non-active tokens");
        }

        IERC20 orphan = IERC20(asset);
        orphan.transfer(guardian(), orphan.balanceOf(address(this)));
    }

    //==================================================================//
    //----------------- PUBLIC/EXTERNAL VIEW FUNCTIONS -----------------//
    //==================================================================//

    /**
     * @dev Gets the pending collateral gains for a user
     * @param _user Address of the user
     * @return An array of CollateralGain structs representing pending gains
     */
    function getPendingCollateralGains(address _user) external view override returns (IBackstopPool.CollateralGain[] memory) {
        return _getPendingCollateralGains(_user);
    }

    /**
     * @dev Gets the pending collateral gain for a specific asset and user
     * @param asset Address of the collateral asset
     * @param _user Address of the user
     * @return The pending collateral gain amount
     */
    function getPendingCollateralGain(address asset, address _user) external view override returns (uint) {
        IBackstopPool.CollateralGain[] memory gains = _getPendingCollateralGains(_user);

        for (uint idx; idx < gains.length; idx++) {
            if (gains[idx].asset == asset) {
                return gains[idx].gains;
            }
        }

        return 0;
    }

    /**
     * @dev Gets the pending stable token gain for a user
     * @param _user Address of the user
     * @return The pending stable token gain amount
     */
    function getPendingStableGain(address _user) external view override returns (uint) {
        return _getPendingStableGain(_user);
    }

    //==================================================================//
    //---------------------- INTERNAL FUNCTIONS ------------------------//
    //==================================================================//

    /**
     * @dev Calculates the pending collateral gains for a user
     * @param _user Address of the user
     * @return An array of CollateralGain structs representing pending gains
     */
    function _getPendingCollateralGains(address _user) internal view returns (IBackstopPool.CollateralGain[] memory) {
        address[] memory collaterals = collateralController.getUniqueActiveCollateralAddresses();
        IBackstopPool.CollateralGain[] memory gains = new IBackstopPool.CollateralGain[](collaterals.length);

        for (uint idx; idx < collaterals.length; idx++) {
            uint F_Collateral_Snapshot = snapshots[_user].F_Collateral_Snapshot[collaterals[idx]];
            uint gain = (feeTokenStakes[_user] * (F_Collateral[collaterals[idx]] - F_Collateral_Snapshot)) / DECIMAL_PRECISION;
            gains[idx] = IBackstopPool.CollateralGain(collaterals[idx], gain);
        }

        return gains;
    }

    /**
     * @dev Calculates the pending stable token gain for a user
     * @param _user Address of the user
     * @return The pending stable token gain amount
     */
    function _getPendingStableGain(address _user) internal view returns (uint) {
        uint F_Stable_Snapshot = snapshots[_user].F_STABLE_Snapshot;
        uint StableGain = (feeTokenStakes[_user] * (F_STABLE - F_Stable_Snapshot)) / DECIMAL_PRECISION;
        return StableGain;
    }

    /**
     * @dev Updates the reward snapshots for a user
     * @param _user Address of the user
     */
    function _updateUserSnapshots(address _user) internal {
        address[] memory collaterals = collateralController.getUniqueActiveCollateralAddresses();
        snapshots[_user].F_STABLE_Snapshot = F_STABLE;

        for (uint idx; idx < collaterals.length; idx++) {
            address asset = collaterals[idx];
            snapshots[_user].F_Collateral_Snapshot[asset] = F_Collateral[asset];
            emit StakerSnapshotsUpdated(_user, F_Collateral[asset], F_STABLE);
        }
    }

    /**
     * @dev Sends collateral gains to a user
     * @param collateralGains An array of CollateralGain structs representing gains to send
     */
    function _sendCollateralGainsToUser(IBackstopPool.CollateralGain[] memory collateralGains) internal {
        for (uint idx; idx < collateralGains.length; idx++) {
            address asset = collateralGains[idx].asset;
            uint gains = collateralGains[idx].gains;

            if (gains != 0) {
                require(
                    IERC20(asset).transfer(msg.sender, gains),
                    "FeeTokenStaking: Failed to send accumulated collateral gain"
                );

                emit StakingCollateralWithdrawn(asset, msg.sender, gains);
            }
        }
    }

    /**
     * @dev Checks if the caller is the PositionManager for the given asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral asset
     */
    function _requireCallerIsPositionManager(address asset, uint8 version) internal view {
        address pm = address(collateralController.getCollateralInstance(asset, version).positionManager);
        require(msg.sender == pm, "FeeTokenStaking: caller is not PositionM");
    }

    /**
    * @dev Checks if the caller is the PositionController or a valid PositionManager
    */
    function _requireCallerIsPCorPM() internal view {
        require(
            (msg.sender == positionControllerAddress) ||
            collateralController.validPositionManager(msg.sender),
            "FeeTokenStaking: caller is not PositionController or PositionManager"
        );
    }

    /**
     * @dev Checks if the caller is the PositionController
     */
    function _requireCallerIsPositionController() internal view {
        require(msg.sender == positionControllerAddress, "FeeTokenStaking: caller is not PositionController");
    }

    /**
     * @dev Checks if the user has a non-zero stake
     * @param currentStake The current stake of the user
     */
    function _requireUserHasStake(uint currentStake) internal pure {
        require(currentStake > 0, 'FeeTokenStaking: User must have a non-zero stake');
    }

    /**
     * @dev Checks if the amount is non-zero
     * @param _amount The amount to check
     */
    function _requireNonZeroAmount(uint _amount) internal pure {
        require(_amount > 0, 'FeeTokenStaking: Amount must be non-zero');
    }
}
BackstopPool.sol 695 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../common/Base.sol";
import "../common/StableMath.sol";
import "../Guardable.sol";
import "../interfaces/IBackstopPool.sol";
import "../interfaces/IRecoverable.sol";
import "../interfaces/ICollateralController.sol";
import "../interfaces/IPositionController.sol";
import "../incentives/BackstopPoolIncentives.sol";
import "../interfaces/IStable.sol";

/**
 * @title BackstopPool
 * @dev Contract for managing a backstop pool in a DeFi system.
 *
 * Key features:
 *   1. Deposit Management: Handles deposits of stable tokens into the backstop pool.
 *   2. Collateral Tracking: Tracks multiple types of collateral assets.
 *   3. Incentive Distribution: Manages the distribution of fee tokens as incentives.
 *   4. Position Offsetting: Allows for offsetting of positions with collateral and debt.
 *   5. Compound Interest: Implements a compound interest mechanism for deposits.
 *   6. Snapshots: Maintains snapshots of user deposits and global state for accurate reward calculations.
 */
contract BackstopPool is Base, Ownable, Guardable, IBackstopPool, IRecoverable {
    string constant public NAME = "BackstopPool";

    // External contract interfaces
    ICollateralController public collateralController;
    IPositionController public positionController;
    IStable public stableToken;
    IBackstopPoolIncentives public incentivesIssuance;

    // Total deposits in the pool
    uint256 internal totalStableDeposits;

    /**
     * @dev Struct to hold collateral totals and related data
     */
    struct CollateralTotals {
        uint256 total;
        mapping(uint128 => mapping(uint128 => uint)) epochToScaleToSum;
        uint lastCollateralError_Offset;
    }

    // Mappings for tracking rewards and collateral
    mapping(uint128 => mapping(uint128 => uint)) public epochToScaleToG;
    mapping(address => CollateralTotals) public collateralToTotals;

    /**
     * @dev Struct to represent a user's deposit
     */
    struct Deposit {
        uint initialValue;
    }

    /**
     * @dev Struct to hold snapshot data for deposits
     */
    struct Snapshots {
        mapping(address => uint) S;
        uint P;
        uint G;
        uint128 scale;
        uint128 epoch;
    }

    // Mappings for user deposits and snapshots
    mapping(address => Deposit) public deposits;
    mapping(address => Snapshots) public depositSnapshots;

    // Global state variables
    uint public P = DECIMAL_PRECISION;
    uint public constant SCALE_FACTOR = 1e9;
    uint128 public currentScale;
    uint128 public currentEpoch;

    // Error tracking for fee token and stable loss
    uint public lastFeeTokenError;
    uint public lastStableLossError_Offset;

    /**
     * @dev Sets the addresses for various components of the system
     * @param _collateralController Address of the collateral controller
     * @param _stableTokenAddress Address of the stable token
     * @param _positionController Address of the position controller
     * @param _incentivesIssuance Address of the incentives issuance contract
     */
    function setAddresses(
        address _collateralController,
        address _stableTokenAddress,
        address _positionController,
        address _incentivesIssuance
    ) external onlyOwner {
        collateralController = ICollateralController(_collateralController);
        incentivesIssuance = IBackstopPoolIncentives(_incentivesIssuance);
        stableToken = IStable(_stableTokenAddress);
        positionController = IPositionController(_positionController);
        renounceOwnership();
    }

    /**
     * @dev Gets the total amount of a specific collateral in the pool
     * @param collateral Address of the collateral token
     * @return The total amount of the specified collateral
     */
    function getCollateral(address collateral) external view override returns (uint) {
        return collateralToTotals[collateral].total;
    }

    /**
     * @dev Gets the total amount of stable token deposits in the pool
     * @return The total amount of stable token deposits
     */
    function getTotalStableDeposits() external view override returns (uint) {
        return totalStableDeposits;
    }

    /**
     * @dev Allows a user to provide funds to the backstop pool
     * @param _amount The amount of stable tokens to deposit
     */
    function provideToBP(uint _amount) external override {
        _requireNonZeroAmount(_amount);
        uint initialDeposit = deposits[msg.sender].initialValue;

        IBackstopPoolIncentives incentiveIssuanceCached = incentivesIssuance;
        _triggerFeeTokenIssuance(incentiveIssuanceCached);

        uint compoundedStableDeposit = getCompoundedStableDeposit(msg.sender);
        uint StableLoss = initialDeposit - compoundedStableDeposit;

        _payOutFeeTokenGains(incentiveIssuanceCached, msg.sender);
        _sendStableToBackstopPool(msg.sender, _amount);

        uint newDeposit = compoundedStableDeposit + _amount;
        CollateralGain[] memory depositorCollateralGains =
                        _calculateGainsAndUpdateSnapshots(msg.sender, newDeposit, false, address(0), 0, address(0), address(0));

        emit UserDepositChanged(msg.sender, newDeposit);
        emit CollateralGainsWithdrawn(msg.sender, depositorCollateralGains, StableLoss);
    }

    /**
     * @dev Allows a user to withdraw funds from the backstop pool
     * @param _amount The amount of stable tokens to withdraw
     */
    function withdrawFromBP(uint _amount) external override {
        if (_amount != 0) {
            _requireNoUnderCollateralizedPositions();
        }

        uint initialDeposit = deposits[msg.sender].initialValue;
        _requireUserHasDeposit(initialDeposit);

        IBackstopPoolIncentives incentiveIssuanceCached = incentivesIssuance;
        _triggerFeeTokenIssuance(incentiveIssuanceCached);

        uint compoundedStableDeposit = getCompoundedStableDeposit(msg.sender);
        uint StabletoWithdraw = StableMath._min(_amount, compoundedStableDeposit);
        uint StableLoss = initialDeposit - compoundedStableDeposit;

        _payOutFeeTokenGains(incentiveIssuanceCached, msg.sender);
        _sendStableToDepositor(msg.sender, StabletoWithdraw);

        uint newDeposit = compoundedStableDeposit - StabletoWithdraw;

        CollateralGain[] memory depositorCollateralGains =
                        _calculateGainsAndUpdateSnapshots(msg.sender, newDeposit, false, address(0), 0, address(0), address(0));

        emit UserDepositChanged(msg.sender, newDeposit);
        emit CollateralGainsWithdrawn(msg.sender, depositorCollateralGains, StableLoss);
    }

    /**
     * @dev Internal function to calculate gains and update snapshots
     * @param depositor Address of the depositor
     * @param newDeposit New deposit amount
     * @param withdrawingToPosition Flag indicating if withdrawing to a position
     * @param asset Address of the asset
     * @param version Version of the asset
     * @param _upperHint Upper hint for position
     * @param _lowerHint Lower hint for position
     * @return depositorCollateralGains Array of collateral gains
     */
    function _calculateGainsAndUpdateSnapshots(
        address depositor, uint newDeposit,
        bool withdrawingToPosition,
        address asset, uint8 version, address _upperHint, address _lowerHint
    ) private returns (CollateralGain[] memory depositorCollateralGains) {
        depositorCollateralGains = getDepositorCollateralGains(depositor);
        _updateDepositAndSnapshots(depositor, newDeposit);

        for (uint idx = 0; idx < depositorCollateralGains.length; idx++) {
            CollateralGain memory gain = depositorCollateralGains[idx];

            if (gain.gains == 0) {
                if (withdrawingToPosition && depositorCollateralGains[idx].asset == asset) {
                    revert("BackstopPool: caller must have non-zero Collateral Gain");
                }
                continue;
            }

            collateralToTotals[gain.asset].total -= gain.gains;
            emit BackstopPoolCollateralBalanceUpdated(gain.asset, collateralToTotals[gain.asset].total);
            emit CollateralSent(gain.asset, depositor, gain.gains);

            if (withdrawingToPosition && depositorCollateralGains[idx].asset == asset) {
                IERC20(asset).approve(address(positionController), gain.gains);
                positionController.moveCollateralGainToPosition(asset, version, gain.gains, depositor, _upperHint, _lowerHint);
            } else {
                require(IERC20(gain.asset).transfer(depositor, gain.gains), "BackstopPool: sending Collateral failed");
            }
        }
    }

    /**
     * @dev Allows a user to withdraw collateral gain to a position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _upperHint Upper hint for position
     * @param _lowerHint Lower hint for position
     */
    function withdrawCollateralGainToPosition(address asset, uint8 version, address _upperHint, address _lowerHint) external override {
        uint initialDeposit = deposits[msg.sender].initialValue;
        _requireUserHasDeposit(initialDeposit);
        _requireUserHasPosition(asset, version, msg.sender);

        IBackstopPoolIncentives incentiveIssuanceCached = incentivesIssuance;
        _triggerFeeTokenIssuance(incentiveIssuanceCached);

        uint compoundedStableDeposit = getCompoundedStableDeposit(msg.sender);
        uint StableLoss = initialDeposit - compoundedStableDeposit;

        _payOutFeeTokenGains(incentiveIssuanceCached, msg.sender);

        CollateralGain[] memory depositorCollateralGains =
                        _calculateGainsAndUpdateSnapshots(msg.sender, compoundedStableDeposit, true, asset, version, _upperHint, _lowerHint);

        emit UserDepositChanged(msg.sender, compoundedStableDeposit);
        emit CollateralGainsWithdrawn(msg.sender, depositorCollateralGains, StableLoss);
    }

    /**
     * @dev Offsets a position with collateral and debt
     * @param collateralAsset Address of the collateral asset
     * @param version Version of the collateral
     * @param _debtToOffset Amount of debt to offset
     * @param _collToAdd Amount of collateral to add
     */
    function offset(address collateralAsset, uint8 version, uint _debtToOffset, uint _collToAdd) external override {
        _requireCallerIsPositionManager(collateralAsset, version);
        uint totalStable = totalStableDeposits;
        if (totalStable == 0 || _debtToOffset == 0) {
            return;
        }

        _triggerFeeTokenIssuance(incentivesIssuance);

        (uint CollateralGainPerUnitStaked, uint StableLossPerUnitStaked) =
                        _computeRewardsPerUnitStaked(collateralAsset, _collToAdd, _debtToOffset, totalStable);

        _updateRewardSumAndProduct(collateralAsset, CollateralGainPerUnitStaked, StableLossPerUnitStaked);
        _moveOffsetCollAndDebt(collateralController.getCollateralInstance(collateralAsset, version), _collToAdd, _debtToOffset);
    }

    /**
     * @dev Computes rewards per unit staked
     * @param collateralAsset Address of the collateral asset
     * @param _collToAdd Amount of collateral to add
     * @param _debtToOffset Amount of debt to offset
     * @param _totalStableDeposits Total stable deposits
     * @return CollateralGainPerUnitStaked Collateral gain per unit staked
     * @return stableLossPerUnitStaked Stable loss per unit staked
     */
    function _computeRewardsPerUnitStaked(
        address collateralAsset,
        uint _collToAdd,
        uint _debtToOffset,
        uint _totalStableDeposits
    ) internal returns (uint CollateralGainPerUnitStaked, uint stableLossPerUnitStaked) {
        uint CollateralNumerator = (_collToAdd * DECIMAL_PRECISION) + collateralToTotals[collateralAsset].lastCollateralError_Offset;
        assert(_debtToOffset <= _totalStableDeposits);

        if (_debtToOffset == _totalStableDeposits) {
            stableLossPerUnitStaked = DECIMAL_PRECISION;
            lastStableLossError_Offset = 0;
        } else {
            uint stableLossNumerator = (_debtToOffset * DECIMAL_PRECISION) - lastStableLossError_Offset;
            stableLossPerUnitStaked = (stableLossNumerator / _totalStableDeposits) + 1;
            lastStableLossError_Offset = (stableLossPerUnitStaked * _totalStableDeposits) - stableLossNumerator;
        }

        CollateralGainPerUnitStaked = CollateralNumerator / _totalStableDeposits;
        collateralToTotals[collateralAsset].lastCollateralError_Offset = CollateralNumerator - (CollateralGainPerUnitStaked * _totalStableDeposits);

        return (CollateralGainPerUnitStaked, stableLossPerUnitStaked);
    }

    /**
     * @dev Updates reward sum and product
     * @param collateralAsset Address of the collateral asset
     * @param _CollateralGainPerUnitStaked Collateral gain per unit staked
     * @param _stableLossPerUnitStaked Stable loss per unit staked
     */
    function _updateRewardSumAndProduct(address collateralAsset, uint _CollateralGainPerUnitStaked, uint _stableLossPerUnitStaked) internal {
        uint currentP = P;
        uint newP;

        assert(_stableLossPerUnitStaked <= DECIMAL_PRECISION);
        uint newProductFactor = uint(DECIMAL_PRECISION) - _stableLossPerUnitStaked;

        uint128 currentScaleCached = currentScale;
        uint128 currentEpochCached = currentEpoch;

        uint currentS = collateralToTotals[collateralAsset].epochToScaleToSum[currentEpochCached][currentScaleCached];
        uint marginalCollateralGain = _CollateralGainPerUnitStaked * currentP;
        uint newS = currentS + marginalCollateralGain;
        collateralToTotals[collateralAsset].epochToScaleToSum[currentEpochCached][currentScaleCached] = newS;
        emit S_Updated(collateralAsset, newS, currentEpochCached, currentScaleCached);

        if (newProductFactor == 0) {
            currentEpoch = currentEpochCached + 1;
            emit EpochUpdated(currentEpoch);
            currentScale = 0;
            emit ScaleUpdated(currentScale);
            newP = DECIMAL_PRECISION;
        } else if (((currentP * newProductFactor) / DECIMAL_PRECISION) < SCALE_FACTOR) {
            newP = ((currentP * newProductFactor) * SCALE_FACTOR) / DECIMAL_PRECISION;
            currentScale = currentScaleCached + 1;
            emit ScaleUpdated(currentScale);
        } else {
            newP = (currentP * newProductFactor) / DECIMAL_PRECISION;
        }

        assert(newP > 0);
        P = newP;

        emit P_Updated(newP);
    }

    /**
     * @dev Extracts orphaned tokens from the contract
     * @param asset Address of the token to extract
     * @param version Version of the token
     */
    function extractOrphanedTokens(address asset, uint8 version) external override onlyGuardian {
        require(asset != address(stableToken), "Naughty...");

        address[] memory collaterals = collateralController.getUniqueActiveCollateralAddresses();
        for (uint idx; idx < collaterals.length; idx++) {
            // Should not be able to extract tokens in the contract which are under normal operation.
            // Only tokens which are not claimed by users before sunset can be extracted,
            // or tokens which are accidentally sent to the contract.
            require(collaterals[idx] != asset, "Guardian can only extract non-active tokens");
        }

        IERC20 orphan = IERC20(asset);
        orphan.transfer(guardian(), orphan.balanceOf(address(this)));
    }

    /**
     * @dev Moves offset collateral and debt
     * @param collateral Collateral instance
     * @param _collToAdd Amount of collateral to add
     * @param _debtToOffset Amount of debt to offset
     */
    function _moveOffsetCollAndDebt(ICollateralController.Collateral memory collateral, uint _collToAdd, uint _debtToOffset) internal {
        IActivePool activePoolCached = collateral.activePool;
        activePoolCached.decreaseStableDebt(_debtToOffset);
        _decreaseStable(_debtToOffset);
        stableToken.burn(address(this), _debtToOffset);
        activePoolCached.sendCollateral(address(this), _collToAdd);
        collateralToTotals[address(collateral.asset)].total += _collToAdd;
        emit BackstopPoolCollateralBalanceUpdated(address(collateral.asset), collateralToTotals[address(collateral.asset)].total);
    }

    /**
     * @dev Decreases the total stable deposits
     * @param _amount Amount to decrease
     */
    function _decreaseStable(uint _amount) internal {
        uint newTotalStableDeposits = totalStableDeposits - _amount;
        totalStableDeposits = newTotalStableDeposits;
        emit BackstopPoolStableBalanceUpdated(newTotalStableDeposits);
    }

    /**
     * @dev Gets the collateral gains for a depositor
     * @param _depositor Address of the depositor
     * @return An array of CollateralGain structs
     */
    function getDepositorCollateralGains(address _depositor) public view override returns (IBackstopPool.CollateralGain[] memory) {
        uint P_Snapshot = depositSnapshots[_depositor].P;
        uint128 epochSnapshot = depositSnapshots[_depositor].epoch;
        uint128 scaleSnapshot = depositSnapshots[_depositor].scale;

        uint initialDeposit = deposits[_depositor].initialValue;
        if (initialDeposit == 0) {return new IBackstopPool.CollateralGain[](0);}

        address[] memory collaterals = collateralController.getUniqueActiveCollateralAddresses();
        IBackstopPool.CollateralGain[] memory gains = new IBackstopPool.CollateralGain[](collaterals.length);

        for (uint idx; idx < collaterals.length; idx++) {
            CollateralTotals storage c = collateralToTotals[collaterals[idx]];

            uint S_Snapshot = depositSnapshots[_depositor].S[collaterals[idx]];
            uint firstPortion = c.epochToScaleToSum[epochSnapshot][scaleSnapshot] - S_Snapshot;
            uint secondPortion = c.epochToScaleToSum[epochSnapshot][scaleSnapshot + 1] / SCALE_FACTOR;
            uint gain = ((initialDeposit * (firstPortion + secondPortion)) / P_Snapshot) / DECIMAL_PRECISION;

            gains[idx] = CollateralGain(collaterals[idx], gain);
        }

        return gains;
    }

    /**
     * @dev Gets the collateral gain for a specific depositor and asset
     * @param asset Address of the collateral asset
     * @param _depositor Address of the depositor
     * @return The amount of collateral gain
     */
    function getDepositorCollateralGain(address asset, address _depositor) external view returns (uint) {
        IBackstopPool.CollateralGain[] memory gains = getDepositorCollateralGains(_depositor);
        for (uint idx; idx < gains.length; idx++) {
            if (gains[idx].asset == asset) {
                return gains[idx].gains;
            }
        }
        return 0;
    }

    /**
     * @dev Gets the sum for a specific epoch and scale
     * @param asset Address of the collateral asset
     * @param epoch Epoch number
     * @param scale Scale number
     * @return The sum for the given epoch and scale
     */
    function getEpochToScaleToSum(address asset, uint128 epoch, uint128 scale) external override view returns (uint) {
        return collateralToTotals[asset].epochToScaleToSum[epoch][scale];
    }

    /**
     * @dev Gets the sum from the deposit snapshot for a specific user and asset
     * @param user Address of the user
     * @param asset Address of the collateral asset
     * @return The sum from the deposit snapshot
     */
    function getDepositSnapshotToAssetToSum(address user, address asset) external view returns (uint) {
        return depositSnapshots[user].S[asset];
    }

    /**
     * @dev Calculates the compounded stable deposit for a depositor
     * @param _depositor Address of the depositor
     * @return The compounded stable deposit amount
     */
    function getCompoundedStableDeposit(address _depositor) public view override returns (uint) {
        uint initialDeposit = deposits[_depositor].initialValue;
        if (initialDeposit == 0) {return 0;}

        uint snapshot_P = depositSnapshots[_depositor].P;
        uint128 scaleSnapshot = depositSnapshots[_depositor].scale;
        uint128 epochSnapshot = depositSnapshots[_depositor].epoch;

        if (epochSnapshot < currentEpoch) {return 0;}

        uint compoundedStake;
        uint128 scaleDiff = currentScale - scaleSnapshot;

        if (scaleDiff == 0) {
            compoundedStake = (initialDeposit * P) / snapshot_P;
        } else if (scaleDiff == 1) {
            compoundedStake = ((initialDeposit * P) / (snapshot_P)) / SCALE_FACTOR;
        } else {
            compoundedStake = 0;
        }

        return (compoundedStake < (initialDeposit / 1e9)) ? 0 : compoundedStake;
    }

    /**
     * @dev Sends stable tokens to the backstop pool
     * @param _address Address to send from
     * @param _amount Amount to send
     */
    function _sendStableToBackstopPool(address _address, uint _amount) internal {
        stableToken.sendToPool(_address, address(this), _amount);
        uint newTotalStableDeposits = totalStableDeposits + _amount;
        totalStableDeposits = newTotalStableDeposits;
        emit BackstopPoolStableBalanceUpdated(newTotalStableDeposits);
    }

    /**
     * @dev Sends stable tokens to a depositor
     * @param _depositor Address of the depositor
     * @param stableWithdrawal Amount to withdraw
     */
    function _sendStableToDepositor(address _depositor, uint stableWithdrawal) internal {
        if (stableWithdrawal == 0) {
            return;
        }
        stableToken.returnFromPool(address(this), _depositor, stableWithdrawal);
        _decreaseStable(stableWithdrawal);
    }

    /**
     * @dev Updates deposit and snapshots for a user
     * @param _depositor Address of the depositor
     * @param _newValue New deposit value
     */
    function _updateDepositAndSnapshots(address _depositor, uint _newValue) internal {
        deposits[_depositor].initialValue = _newValue;
        address[] memory collaterals = collateralController.getUniqueActiveCollateralAddresses();

        if (_newValue == 0) {
            delete depositSnapshots[_depositor];
            for (uint idx; idx < collaterals.length; idx++) {
                depositSnapshots[_depositor].S[collaterals[idx]] = 0;
            }
            emit DepositSnapshotUpdated(_depositor, address(0), 0, 0, 0);
            return;
        }

        uint128 currentScaleCached = currentScale;
        uint128 currentEpochCached = currentEpoch;
        uint currentG = epochToScaleToG[currentEpochCached][currentScaleCached];
        uint currentP = P;

        for (uint idx; idx < collaterals.length; idx++) {
            CollateralTotals storage c = collateralToTotals[collaterals[idx]];
            uint currentS = c.epochToScaleToSum[currentEpochCached][currentScaleCached];
            depositSnapshots[_depositor].S[collaterals[idx]] = currentS;
            emit DepositSnapshotUpdated(_depositor, collaterals[idx], currentP, currentS, currentG);
        }

        depositSnapshots[_depositor].P = currentP;
        depositSnapshots[_depositor].G = currentG;
        depositSnapshots[_depositor].scale = currentScaleCached;
        depositSnapshots[_depositor].epoch = currentEpochCached;
    }

    /**
     * @dev Triggers fee token issuance
     * @param _incentivesIssuance Address of the incentives issuance contract
     */
    function _triggerFeeTokenIssuance(IBackstopPoolIncentives _incentivesIssuance) internal {
        uint feeTokenIssuance = _incentivesIssuance.issueFeeTokens();
        _updateG(feeTokenIssuance);
    }

    /**
     * @dev Updates the G value
     * @param _feeTokenIssuance Amount of fee tokens issued
     */
    function _updateG(uint _feeTokenIssuance) internal {
        uint totalStable = totalStableDeposits; // cached to save an SLOAD
        /*
        * When total deposits is 0, G is not updated. In this case, the feeToken issued can not be obtained by later
        * depositors - it is missed out on, and remains in the balanceOf the IncentivesIssuance contract.
        */
        if (totalStable == 0 || _feeTokenIssuance == 0) {return;}

        uint feeTokenPerUnitStaked = _computeFeeTokenPerUnitStaked(_feeTokenIssuance, totalStable);
        uint marginalFeeTokenGain = feeTokenPerUnitStaked * P;
        epochToScaleToG[currentEpoch][currentScale] = epochToScaleToG[currentEpoch][currentScale] + marginalFeeTokenGain;

        emit G_Updated(epochToScaleToG[currentEpoch][currentScale], currentEpoch, currentScale);
    }

    /**
     * @dev Computes fee token per unit staked
     * @param _feeTokenIssuance Amount of fee tokens issued
     * @param _totalStableDeposits Total stable deposits
     * @return The computed fee token per unit staked
     */
    function _computeFeeTokenPerUnitStaked(uint _feeTokenIssuance, uint _totalStableDeposits) internal returns (uint) {
        /*
        * Calculate the feeToken-per-unit staked.  Division uses a "feedback" error correction, to keep the
        * cumulative error low in the running total G:
        *
        * 1) Form a numerator which compensates for the floor division error that occurred the last time this
        * function was called.
        * 2) Calculate "per-unit-staked" ratio.
        * 3) Multiply the ratio back by its denominator, to reveal the current floor division error.
        * 4) Store this error for use in the next correction when this function is called.
        * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
        */
        uint feeTokenNumerator = (_feeTokenIssuance * DECIMAL_PRECISION) + lastFeeTokenError;

        uint feeTokenPerUnitStaked = feeTokenNumerator / _totalStableDeposits;
        lastFeeTokenError = feeTokenNumerator - (feeTokenPerUnitStaked * _totalStableDeposits);

        return feeTokenPerUnitStaked;
    }

    /**
     * @dev Calculates the fee token gain for a depositor
     * @param _depositor Address of the depositor
     * @return The calculated fee token gain
     */
    function getDepositorFeeTokenGain(address _depositor) public view override returns (uint) {
        uint initialDeposit = deposits[_depositor].initialValue;
        if (initialDeposit == 0) {return 0;}
        Snapshots storage snapshots = depositSnapshots[_depositor];
        return (DECIMAL_PRECISION * (_getFeeTokenGainFromSnapshots(initialDeposit, snapshots))) / DECIMAL_PRECISION;
    }

    /**
     * @dev Gets the fee token gain from snapshots
     * @param initialStake Initial stake amount
     * @param snapshots Snapshots struct
     * @return The calculated fee token gain
     */
    function _getFeeTokenGainFromSnapshots(uint initialStake, Snapshots storage snapshots) internal view returns (uint) {
        /*
         * Grab the sum 'G' from the epoch at which the stake was made. The feeToken gain may span up to one scale change.
         * If it does, the second portion of the feeToken gain is scaled by 1e9.
         * If the gain spans no scale change, the second portion will be 0.
         */
        uint128 epochSnapshot = snapshots.epoch;
        uint128 scaleSnapshot = snapshots.scale;
        uint G_Snapshot = snapshots.G;
        uint P_Snapshot = snapshots.P;

        uint firstPortion = epochToScaleToG[epochSnapshot][scaleSnapshot] - G_Snapshot;
        uint secondPortion = epochToScaleToG[epochSnapshot][scaleSnapshot + 1] / SCALE_FACTOR;

        return ((initialStake * (firstPortion + secondPortion)) / P_Snapshot) / DECIMAL_PRECISION;
    }

    /**
     * @dev Pays out fee token gains to a depositor
     * @param _incentivesIssuance Incentives issuance contract
     * @param _depositor Address of the depositor
     */
    function _payOutFeeTokenGains(IBackstopPoolIncentives _incentivesIssuance, address _depositor) internal {
        uint depositorFeeTokenGain = getDepositorFeeTokenGain(_depositor);
        _incentivesIssuance.sendFeeTokens(_depositor, depositorFeeTokenGain);
        emit FeeTokenPaidToDepositor(_depositor, depositorFeeTokenGain);
    }

    /**
     * @dev Checks if the caller is the position manager for the given collateral and version
     * @param collateralAsset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireCallerIsPositionManager(address collateralAsset, uint8 version) internal view {
        require(
            msg.sender == address(collateralController.getCollateralInstance(collateralAsset, version).positionManager),
            "BackstopPool: Caller is not a PositionManager"
        );
    }

    /**
     * @dev Checks if there are no under-collateralized positions
     */
    function _requireNoUnderCollateralizedPositions() internal {
        collateralController.requireNoUnderCollateralizedPositions();
    }

    /**
     * @dev Checks if a user has a deposit
     * @param _initialDeposit Initial deposit amount
     */
    function _requireUserHasDeposit(uint _initialDeposit) internal pure {
        require(_initialDeposit > 0, 'BackstopPool: User must have a non-zero deposit');
    }

    /**
     * @dev Checks if the amount is non-zero
     * @param _amount Amount to check
     */
    function _requireNonZeroAmount(uint _amount) internal pure {
        require(_amount > 0, 'BackstopPool: Amount must be non-zero');
    }

    /**
     * @dev Checks if a user has an active position for the given asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _depositor Address of the depositor
     */
    function _requireUserHasPosition(address asset, uint8 version, address _depositor) internal view {
        require(
            collateralController.getCollateralInstance(asset, version).positionManager.getPositionStatus(_depositor) == 1,
            "BackstopPool: caller must have an active position to withdraw CollateralGain to"
        );
    }
}
BaseRateAbstract.sol 100 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../common/Base.sol";
import "../common/StableMath.sol";

/**
 * @title BaseRateAbstract
 * @dev Abstract contract for managing a dynamic base rate
 * The base rate is used to calculate fees for redemptions and new issuances of debt.
 * It decays over time and increases based on redemption volume.
 */
abstract contract BaseRateAbstract is Base {
    // Current base rate, represented with precision DECIMAL_PRECISION
    uint public _baseRate;

    // Timestamp of the latest fee operation (redemption or new stable issuance)
    uint public _lastFeeOperationTime;

    event BaseRateUpdated(uint newBaseRate);
    event LastFeeOpTimeUpdated(uint lastFeeOpTime);

    /**
     * @dev Updates the base rate based on a redemption operation
     * @param _CollateralDrawn Amount of collateral drawn for redemption
     * @param _price Price of the collateral
     * @param _totalStableSupply Total supply of the stablecoin
     * @return The new base rate after the update
     */
    function _updateBaseRateFromRedemption(uint _CollateralDrawn, uint _price, uint _totalStableSupply) internal returns (uint) {
        // First, decay the base rate based on time passed since last fee operation
        uint decayedBaseRate = _calcDecayedBaseRate();

        // Calculate the fraction of total supply that was redeemed at face value
        uint redeemedStableFraction = (_CollateralDrawn * _price) / _totalStableSupply;

        // Increase the base rate based on the redeemed fraction
        uint newBaseRate = decayedBaseRate + (redeemedStableFraction / BETA);
        newBaseRate = StableMath._min(newBaseRate, StableMath.DECIMAL_PRECISION); // Cap base rate at 100%
        assert(newBaseRate > 0); // Base rate is always non-zero after redemption

        // Update the base rate state variable
        _baseRate = newBaseRate;
        emit BaseRateUpdated(newBaseRate);

        _updateLastFeeOpTime();

        return newBaseRate;
    }

    /**
     * @dev Calculates the decayed base rate based on time passed since last fee operation
     * @return The decayed base rate
     */
    function _calcDecayedBaseRate() internal view returns (uint) {
        uint minutesPassed = _minutesPassedSinceLastFeeOp();
        uint decayFactor = StableMath._decPow(MINUTE_DECAY_FACTOR, minutesPassed);
        return (_baseRate * decayFactor) / StableMath.DECIMAL_PRECISION;
    }

    /**
     * @dev Calculates the number of minutes passed since the last fee operation
     * @return Number of minutes passed
     */
    function _minutesPassedSinceLastFeeOp() internal view returns (uint) {
        return (block.timestamp - _lastFeeOperationTime) / SECONDS_IN_ONE_MINUTE;
    }

    /**
     * @dev Updates the last fee operation time
     * Only updates if at least one minute has passed to prevent base rate griefing.
     * _lastFeeOperationTime is incremented in multiples of SECONDS_IN_ONE_MINUTE, so that seconds in the current
     * minute are not ignored for the purposes of baseRate decay.
     */
    function _updateLastFeeOpTime() internal {
        uint minutesPassed = _minutesPassedSinceLastFeeOp();

        if (minutesPassed > 0) {
            _lastFeeOperationTime += minutesPassed * SECONDS_IN_ONE_MINUTE;
            emit LastFeeOpTimeUpdated(block.timestamp);
        }
    }

    /**
     * @dev Decays the base rate when new stablecoins are borrowed
     * This function is called during borrowing operations to reduce the base rate over time
     */
    function _decayBaseRateFromBorrowing() internal {
        uint decayedBaseRate = _calcDecayedBaseRate();
        assert(decayedBaseRate <= StableMath.DECIMAL_PRECISION); // The base rate can decay to 0

        _baseRate = decayedBaseRate;
        emit BaseRateUpdated(decayedBaseRate);

        _updateLastFeeOpTime();
    }
}
GasCompensationPool.sol 31 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

/**
 * @title GasCompensationPool
 * @dev This contract serves as a designated address for gas compensation in the system.
 * It doesn't contain any active code, but acts as a recipient for certain token operations.
 */
contract GasCompensationPool {
    /**
     * @dev This contract intentionally does not implement any functions.
     * It serves as a designated address that core contracts can interact with.
     *
     * Key points:
     * 1. Core contracts have permission to send tokens to this address.
     * 2. Core contracts can burn tokens from this address.
     * 3. This design allows for gas-efficient operations in the main system.
     */

    /**
     * @dev Note on potential orphaned tokens:
     * In the event a PositionManager is sunset and unclaimed collateral is extracted by the guardian address,
     * some stable tokens may become orphaned in this contract.
     *
     * This is considered acceptable because:
     * 1. These orphaned stables would be unbacked.
     * 2. They are effectively locked/burned forever, as this contract has no withdrawal mechanism.
     * 3. This ensures that unbacked stables are permanently removed from circulation, maintaining system integrity.
     */
}
PermissionedRedeemerAbstract.sol 104 lines
pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title PermissionedRedeemerAbstract
 * @dev Abstract contract implementing a role-based permission system for redemptions.
 *
 * This contract enables a whitelist mechanism for redemptions, where redemptions
 * can be restricted to only whitelisted addresses. The whitelist becomes active
 * as soon as at least one address has been granted the REDEEMER_ROLE.
 *
 * Key features:
 * - Maintains a count of whitelisted redeemers
 * - Automatically activates restrictions when first redeemer is added
 * - Automatically deactivates restrictions when last redeemer is removed
 */
abstract contract PermissionedRedeemerAbstract is AccessControl {
    /// @dev Role identifier for redeemers. Addresses with this role can perform redemptions when restrictions are active.
    bytes32 public constant REDEEMER_ROLE = keccak256("REDEEMER_ROLE");

    /// @dev Counter tracking the number of addresses with REDEEMER_ROLE. Used to determine if whitelist is active.
    uint public countWhitelistedRedeemers;

    /**
     * @dev Constructor that grants the contract deployer the default admin role.
     * The admin can then grant and revoke the REDEEMER_ROLE to other addresses.
     */
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    /**
     * @dev Overrides the OpenZeppelin grantRole to track the number of redeemers.
     * Increments the redeemer counter when a new address is granted the REDEEMER_ROLE.
     * @param role The role being granted
     * @param account The address receiving the role
     */
    function grantRole(bytes32 role, address account) public override {
        if (!hasRole(role, account) && role == REDEEMER_ROLE) {
            countWhitelistedRedeemers++;
        }

        super.grantRole(role, account);
    }

    /**
     * @dev Overrides the OpenZeppelin revokeRole to track the number of redeemers.
     * Decrements the redeemer counter when the REDEEMER_ROLE is revoked from an address.
     * @param role The role being revoked
     * @param account The address losing the role
     */
    function revokeRole(bytes32 role, address account) public override {
        if (hasRole(role, account) && role == REDEEMER_ROLE) {
            countWhitelistedRedeemers--;
        }

        super.revokeRole(role, account);
    }

    /**
     * @dev Overrides the OpenZeppelin renounceRole to track the number of redeemers.
     * Decrements the redeemer counter when an address renounces their REDEEMER_ROLE.
     * @param role The role being renounced
     * @param account The address renouncing the role
     */
    function renounceRole(bytes32 role, address account) public override {
        if (hasRole(role, account) && role == REDEEMER_ROLE) {
            countWhitelistedRedeemers--;
        }

        super.renounceRole(role, account);
    }

    /**
     * @dev Checks if redemptions are currently restricted to whitelisted addresses.
     * Restrictions are active if there is at least one address with REDEEMER_ROLE.
     * @return bool True if redemptions are restricted to whitelisted addresses, false if open to all
     */
    function isRedemptionRestricted() public view returns (bool) {
        return countWhitelistedRedeemers > 0;
    }

    /**
     * @dev Determines if a specific address has permission to perform redemptions.
     * Permission is granted either when no restrictions are active, or when the
     * address has been granted the REDEEMER_ROLE.
     * @param _redeemer Address to check for redemption permissions
     * @return bool True if the address can perform redemptions, false otherwise
     */
    function canRedeem(address _redeemer) public view returns (bool) {
        return !isRedemptionRestricted() || hasRole(REDEEMER_ROLE, _redeemer);
    }

    /**
     * @dev Checks if redemption whitelist is active and the caller lacks the REDEEMER_ROLE.
     * @param user Address to check (currently unused - checks msg.sender instead)
     * @return bool True if whitelist is active and caller lacks REDEEMER_ROLE
     * @notice The user parameter is currently not used - function checks msg.sender instead
     */
    function redemptionWhitelistOnAndUserWithoutRole(address user) public view returns (bool) {
        return isRedemptionRestricted() && !hasRole(REDEEMER_ROLE, msg.sender);
    }
}
PositionController.sol 1097 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "../common/Base.sol";
import "../common/StableMath.sol";
import "../interfaces/ICollateralController.sol";
import "../Guardable.sol";
import "../interfaces/IFeeTokenStaking.sol";
import "../interfaces/IPositionController.sol";
import "../interfaces/IStable.sol";

/**
 * @title PositionController
 * @dev Contract for managing positions, including opening, adjusting, and closing positions.
 *
 * Key features:
 *      1. Position Management: Allows users to open, adjust, and close positions with various collateral types.
 *      2. Collateral Handling: Manages different types of collateral, including deposit and withdrawal.
 *      3. Debt Management: Handles borrowing and repayment of stable tokens.
 *      4. Liquidation and Recovery: Implements recovery mode and handles liquidations.
 *      5. Fee Management: Calculates and applies borrowing fees.
 *      6. Escrow Mechanism: Implements an escrow system for newly minted stable tokens.
 *      7. Referral System: Supports a referral system for position creation.
 *      8. Safety Checks: Implements various safety checks to maintain system stability.
 */
contract PositionController is Base, Ownable, Guardable, IPositionController {
    using SafeERC20 for IERC20Metadata;

    // Address of the backstop pool
    address public backstopPoolAddress;
    // Interface for the stable token
    IStable public stableToken;
    // Interface for fee token staking
    IFeeTokenStaking public feeTokenStaking;
    // Address of the fee token staking contract
    address public feeTokenStakingAddress;
    // Interface for collateral controller
    ICollateralController public collateralController;

    // Mapping to store loan origination escrow for each asset, version, and user
    mapping(address => mapping(uint8 => mapping(address => LoanOriginationEscrow))) public assetToVersionToUserToEscrow;

    /**
     * @dev Struct to represent a loan origination escrow
     */
    struct LoanOriginationEscrow {
        address owner;
        address asset;
        uint8 version;
        uint startTimestamp;
        uint stables;
        uint quotePrice;

        // only set on external reads.  Do not use for contract operations.
        uint loanCooldownPeriod;
    }

    /**
    * @notice Distributes clawback rewards to fee token stakers
    * @param amount Amount of stable tokens to distribute
    */
    function distributeClawbackRewards(uint amount) external onlyGuardian {
        require(amount > 0, "Cannot distribute zero amount");
        require(stableToken.balanceOf(address(this)) >= amount, "Insufficient Balance");
        feeTokenStaking.increaseF_STABLE(amount);
        stableToken.transfer(feeTokenStakingAddress, amount);
    }

    /**
     * @dev Reclaims clawback rewards to multisig
     * @param amount Amount of rewards to reclaim
     * Can only be called by the guardian
     */
    function reclaimClawbackRewards(uint amount) external onlyGuardian {
        require(amount > 0, "Cannot reclaim zero amount");
        require(stableToken.balanceOf(address(this)) >= amount, "Insufficient Balance");
        stableToken.transfer(0x54FDAcea0af4026306A665E9dAB635Ef5fF2963f, amount);
    }

    /**
     * @dev Allows users to claim their escrowed stables
     * @param originator Address of the originator
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function claimEscrow(address originator, address asset, uint8 version) external {
        ICollateralController.Collateral memory c = collateralController.getCollateralInstance(asset, version);
        LoanOriginationEscrow storage escrow = assetToVersionToUserToEscrow[asset][version][originator];
        require(escrow.startTimestamp != 0, "No active escrow for this asset and version");

        (uint requiredEscrowDuration, uint claimGracePeriod) = collateralController.getLoanCooldownRequirement(asset, version);
        uint cooldownExpiry = escrow.startTimestamp + requiredEscrowDuration;

        uint currentPrice = c.priceFeed.fetchLowestPrice(false, false);
        uint gracePeriodExpiry = cooldownExpiry + claimGracePeriod;
        bool wasLiquidated = c.positionManager.getPositionStatus(escrow.owner) == 3;

        if ((block.timestamp < gracePeriodExpiry) && !wasLiquidated) {
            require(msg.sender == escrow.owner, "Only originator can unlock during grace period");
        }

        bool isRecoveryMode = collateralController.checkRecoveryMode(asset, version, currentPrice);
        uint ICR = c.positionManager.getCurrentICR(escrow.owner, currentPrice);

        uint thresholdRatio = isRecoveryMode ?
            collateralController.getCCR(asset, version)
            :
            collateralController.getMCR(asset, version);

        if ((ICR < thresholdRatio) || wasLiquidated) {
            stableToken.mint(address(this), escrow.stables);
        } else {
            require(block.timestamp >= cooldownExpiry, "Escrow claiming cooldown not met");
            stableToken.mint(escrow.owner, escrow.stables);
        }

        delete assetToVersionToUserToEscrow[asset][version][originator];
    }

    /**
     * @dev Retrieves the escrow information for a given account
     * @param account Address of the account
     * @param asset Asset which may have a pending escrow
     * @param version Corresponding asset version
     * @return LoanOriginationEscrow struct containing escrow details
     */
    function getEscrow(address account, address asset, uint8 version) external view returns (LoanOriginationEscrow memory) {
        LoanOriginationEscrow memory loe = assetToVersionToUserToEscrow[asset][version][account];
        (loe.loanCooldownPeriod,) = collateralController.getLoanCooldownRequirement(loe.asset, loe.version);
        return loe;
    }

    /**
     * @dev Struct to hold local variables for adjusting a position
     * Used to avoid stack too deep errors
     */
    struct LocalVariables_adjustPosition {
        uint price;
        uint collChange;
        uint netDebtChange;
        bool isCollIncrease;
        uint debt;
        uint coll;
        uint oldICR;
        uint newICR;
        uint newTCR;
        uint stableFee;
        uint newDebt;
        uint newColl;
        uint stake;
        uint suggestedAdditiveFeePCT;
        uint utilizationPCT;
        uint loadIncrease;
        bool isRecoveryMode;
    }

    /**
     * @dev Struct to hold local variables for opening a position
     * Used to avoid stack too deep errors
     */
    struct LocalVariables_openPosition {
        uint price;
        uint stableFee;
        uint netDebt;
        uint compositeDebt;
        uint ICR;
        uint NICR;
        uint stake;
        uint arrayIndex;
        uint suggestedFeePCT;
        uint utilizationPCT;
        uint loadIncrease;
        bool isRecoveryMode;
        uint requiredEscrowDuration;
    }

    /**
     * @dev Enum to represent different position operations
     */
    enum PositionOperation {
        openPosition,
        closePosition,
        adjustPosition
    }

    /**
     * @dev Sets the addresses for various components of the system
     * @param _collateralController Address of the collateral controller
     * @param _backstopPoolAddress Address of the backstop pool
     * @param _gasPoolAddress Address of the gas pool
     * @param _stableTokenAddress Address of the stable token
     * @param _feeTokenStakingAddress Address of the fee token staking contract
     * Can only be called by the owner
     */
    function setAddresses(
        address _collateralController,
        address _backstopPoolAddress,
        address _gasPoolAddress,
        address _stableTokenAddress,
        address _feeTokenStakingAddress
    ) external override onlyOwner {
        assert(MIN_NET_DEBT > 0);

        backstopPoolAddress = _backstopPoolAddress;
        gasPoolAddress = _gasPoolAddress;
        stableToken = IStable(_stableTokenAddress);
        feeTokenStakingAddress = _feeTokenStakingAddress;
        feeTokenStaking = IFeeTokenStaking(_feeTokenStakingAddress);
        collateralController = ICollateralController(_collateralController);
        renounceOwnership();
    }

    /**
     * @dev Opens a new position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param suppliedCollateral Amount of collateral supplied
     * @param _maxFeePercentage Maximum fee percentage allowed
     * @param _stableAmount Amount of stable tokens to borrow
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function openPosition(
        address asset, uint8 version, uint suppliedCollateral, uint _maxFeePercentage,
        uint _stableAmount, address _upperHint, address _lowerHint
    ) external override {
        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(asset, version);
        LocalVariables_openPosition memory vars;

        collateralController.requireIsActive(asset, version);
        _requireNotSunsetting(address(collateral.asset), collateral.version);

        (vars.utilizationPCT, vars.loadIncrease) = collateralController.regenerateAndConsumeLoanPoints(asset, version, _stableAmount);
        (vars.price, vars.suggestedFeePCT) = collateral.priceFeed.fetchLowestPriceWithFeeSuggestion(
            vars.loadIncrease,
            vars.utilizationPCT,
            true, // Test health of liquidity before issuing more debt
            true  // Test market stability. We want to be extremely conservative with new debt creation, so this check includes SPOT price if spotConsideration is 'true' in the price feed.
        );

        vars.isRecoveryMode = collateralController.checkRecoveryMode(address(collateral.asset), collateral.version, vars.price);
        _requireValidMaxFeePercentage(_maxFeePercentage, vars.isRecoveryMode, asset, version, vars.suggestedFeePCT);
        _requirePositionIsNotActive(collateral.positionManager, msg.sender);

        vars.netDebt = _stableAmount;

        if (!vars.isRecoveryMode) {
            vars.stableFee = _triggerBorrowingFee(
                collateral.positionManager,
                stableToken,
                _stableAmount,
                _maxFeePercentage,
                vars.suggestedFeePCT
            );
            vars.netDebt = vars.netDebt + vars.stableFee;
        }

        _requireAtLeastMinNetDebt(vars.netDebt);

        // ICR is based on the composite debt, i.e. the requested stable amount + stable borrowing fee + stable gas comp.
        vars.compositeDebt = vars.netDebt + GAS_COMPENSATION;
        assert(vars.compositeDebt > 0);

        vars.ICR = StableMath._computeCR(suppliedCollateral, vars.compositeDebt, vars.price, collateral.asset.decimals());
        vars.NICR = StableMath._computeNominalCR(suppliedCollateral, vars.compositeDebt, collateral.asset.decimals());

        if (vars.isRecoveryMode) {
            _requireICRisAboveCCR(vars.ICR, asset, version);
        } else {
            _requireICRisAboveMCR(vars.ICR, asset, version);
            uint newTCR = _getNewTCRFromPositionChange(collateral, suppliedCollateral, true, vars.compositeDebt, true, vars.price);  // bools: coll increase, debt increase
            _requireNewTCRisAboveCCR(newTCR, asset, version);
        }

        _requireDoesNotExceedCap(collateral, vars.compositeDebt);

        collateral.positionManager.setPositionStatus(msg.sender, 1);
        collateral.positionManager.increasePositionColl(msg.sender, suppliedCollateral);
        collateral.positionManager.increasePositionDebt(msg.sender, vars.compositeDebt);

        collateral.positionManager.updatePositionRewardSnapshots(msg.sender);
        vars.stake = collateral.positionManager.updateStakeAndTotalStakes(msg.sender);

        collateral.sortedPositions.insert(msg.sender, vars.NICR, _upperHint, _lowerHint);
        vars.arrayIndex = collateral.positionManager.addPositionOwnerToArray(msg.sender);
        emit PositionCreated(asset, version, msg.sender, vars.arrayIndex);

        // Move the collateral to the Active Pool, and mint the stableAmount to the borrower
        _activePoolAddColl(collateral, suppliedCollateral);
        (vars.requiredEscrowDuration,) = collateralController.getLoanCooldownRequirement(asset, version);
        _withdrawStable(collateral.activePool, stableToken, msg.sender, _stableAmount, vars.netDebt, vars.requiredEscrowDuration != 0, asset, version, vars.price);
        // Move the stable gas compensation to the Gas Pool
        _withdrawStable(collateral.activePool, stableToken, gasPoolAddress, GAS_COMPENSATION, GAS_COMPENSATION, false, asset, version, vars.price);

        emit PositionUpdated(asset, version, msg.sender, vars.compositeDebt, suppliedCollateral, vars.stake, uint8(PositionOperation.openPosition));
        emit StableBorrowingFeePaid(asset, version, msg.sender, vars.stableFee);
    }

    /**
     * @dev Adds collateral to an existing position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _collAddition Amount of collateral to add
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function addColl(address asset, uint8 version, uint _collAddition, address _upperHint, address _lowerHint) external override {
        _requireNotSunsetting(asset, version);
        _adjustPosition(AdjustPositionParams(asset, version, _collAddition, msg.sender, 0, 0, false, _upperHint, _lowerHint, 0));
    }

    /**
     * @dev Moves collateral gain to a position (only callable by Backstop Pool)
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _collAddition Amount of collateral to add
     * @param _borrower Address of the borrower
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function moveCollateralGainToPosition(address asset, uint8 version, uint _collAddition, address _borrower, address _upperHint, address _lowerHint) external override {
        _requireCallerIsBackstopPool();
        _requireNotSunsetting(asset, version);
        _adjustPosition(AdjustPositionParams(asset, version, _collAddition, _borrower, 0, 0, false, _upperHint, _lowerHint, 0));
    }

    /**
     * @dev Withdraws collateral from an existing position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _collWithdrawal Amount of collateral to withdraw
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function withdrawColl(address asset, uint8 version, uint _collWithdrawal, address _upperHint, address _lowerHint) external override {
        _adjustPosition(AdjustPositionParams(asset, version, 0, msg.sender, _collWithdrawal, 0, false, _upperHint, _lowerHint, 0));
    }

    /**
     * @dev Withdraws stable tokens from a position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _maxFeePercentage Maximum fee percentage allowed
     * @param _stableAmount Amount of stable tokens to withdraw
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function withdrawStable(address asset, uint8 version, uint _maxFeePercentage, uint _stableAmount, address _upperHint, address _lowerHint) external override {
        _requireNotSunsetting(asset, version);
        _adjustPosition(AdjustPositionParams(asset, version, 0, msg.sender, 0, _stableAmount, true, _upperHint, _lowerHint, _maxFeePercentage));
    }

    /**
     * @dev Repays stable tokens to a position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _stableAmount Amount of stable tokens to repay
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function repayStable(address asset, uint8 version, uint _stableAmount, address _upperHint, address _lowerHint) external override {
        _adjustPosition(AdjustPositionParams(asset, version, 0, msg.sender, 0, _stableAmount, false, _upperHint, _lowerHint, 0));
    }

    /**
     * @dev Adjusts an existing position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _collAddition Amount of collateral to add
     * @param _maxFeePercentage Maximum fee percentage allowed
     * @param _collWithdrawal Amount of collateral to withdraw
     * @param _stableChange Amount of stable tokens to change
     * @param _isDebtIncrease Whether the debt is increasing
     * @param _upperHint Upper hint for position insertion
     * @param _lowerHint Lower hint for position insertion
     */
    function adjustPosition(address asset, uint8 version, uint _collAddition, uint _maxFeePercentage, uint _collWithdrawal,
        uint _stableChange, bool _isDebtIncrease, address _upperHint, address _lowerHint) external override {
        if (_collAddition > 0 || (_stableChange > 0 && _isDebtIncrease)) {_requireNotSunsetting(asset, version);}
        _adjustPosition(AdjustPositionParams(asset, version, _collAddition, msg.sender, _collWithdrawal, _stableChange, _isDebtIncrease, _upperHint, _lowerHint, _maxFeePercentage));
    }

    /**
     * @dev Struct to hold parameters for position adjustment
     */
    struct AdjustPositionParams {
        address asset;
        uint8 version;
        uint _collAddition;
        address _borrower;
        uint _collWithdrawal;
        uint _stableChange;
        bool _isDebtIncrease;
        address _upperHint;
        address _lowerHint;
        uint _maxFeePercentage;
    }

    /**
     * @dev Internal function to adjust a position
     * @param params AdjustPositionParams struct containing adjustment parameters
     */
    function _adjustPosition(AdjustPositionParams memory params) internal {
        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(params.asset, params.version);
        LocalVariables_adjustPosition memory vars;

        (vars.utilizationPCT, vars.loadIncrease) = params._isDebtIncrease ?
            collateralController.regenerateAndConsumeLoanPoints(params.asset, params.version, params._stableChange) :
            collateralController.regenerateAndConsumeLoanPoints(params.asset, params.version, 0);

        (vars.price, vars.suggestedAdditiveFeePCT) = collateral.priceFeed.fetchLowestPriceWithFeeSuggestion(
            vars.loadIncrease,
            vars.utilizationPCT,
            params._isDebtIncrease, // Test health of liquidity before issuing more debt
            params._isDebtIncrease  // Test market stability. We want to be extremely conservative with new debt creation, so this check includes SPOT price if spotConsideration is 'true' in the price feed.
        );

        vars.isRecoveryMode = collateralController.checkRecoveryMode(address(collateral.asset), collateral.version, vars.price);

        if (params._isDebtIncrease) {
            _requireValidMaxFeePercentage(params._maxFeePercentage, vars.isRecoveryMode, params.asset, params.version, vars.suggestedAdditiveFeePCT);
            _requireNonZeroDebtChange(params._stableChange);
        }
        _requireSingularCollChange(params._collWithdrawal, params._collAddition);
        _requireNonZeroAdjustment(params._collWithdrawal, params._stableChange, params._collAddition);
        _requirePositionIsActive(collateral.positionManager, params._borrower);

        assert(msg.sender == params._borrower || (msg.sender == backstopPoolAddress && params._collAddition > 0 && params._stableChange == 0));

        collateral.positionManager.applyPendingRewards(params._borrower);

        // Get the collChange based on whether or not Collateral was sent in the transaction
        (vars.collChange, vars.isCollIncrease) = _getCollChange(params._collAddition, params._collWithdrawal);

        vars.netDebtChange = params._stableChange;

        // If the adjustment incorporates a debt increase and system is in Normal Mode, then trigger a borrowing fee
        if (params._isDebtIncrease && !vars.isRecoveryMode) {
            vars.stableFee = _triggerBorrowingFee(
                collateral.positionManager,
                stableToken,
                params._stableChange,
                params._maxFeePercentage,
                vars.suggestedAdditiveFeePCT
            );
            vars.netDebtChange = vars.netDebtChange + vars.stableFee; // The raw debt change includes the fee
        }

        vars.debt = collateral.positionManager.getPositionDebt(params._borrower);
        vars.coll = collateral.positionManager.getPositionColl(params._borrower);

        // Get the Position's old ICR before the adjustment, and what its new ICR will be after the adjustment
        vars.oldICR = StableMath._computeCR(vars.coll, vars.debt, vars.price, collateral.asset.decimals());
        vars.newICR = _getNewICRFromPositionChange(vars.coll, vars.debt, vars.collChange, vars.isCollIncrease,
            vars.netDebtChange, params._isDebtIncrease, vars.price, collateral.asset.decimals());

        assert(params._collWithdrawal <= vars.coll);

        // Check the adjustment satisfies all conditions for the current system mode
        _requireValidAdjustmentInCurrentMode(collateral, vars.isRecoveryMode, params._collWithdrawal, params._isDebtIncrease, vars);

        if (params._isDebtIncrease) {
            _requireDoesNotExceedCap(collateral, vars.netDebtChange);
        }

        // When the adjustment is a debt repayment, check it's a valid amount and that the caller has enough stable
        if (!params._isDebtIncrease && params._stableChange > 0) {
            _requireAtLeastMinNetDebt((vars.debt - GAS_COMPENSATION) - vars.netDebtChange);
            _requireValidStableRepayment(vars.debt, vars.netDebtChange);
            _requireSufficientStableBalance(stableToken, params._borrower, vars.netDebtChange);
        }

        (vars.newColl, vars.newDebt) = _updatePositionFromAdjustment(
            collateral.positionManager, params._borrower, vars.collChange, vars.isCollIncrease, vars.netDebtChange, params._isDebtIncrease
        );

        vars.stake = collateral.positionManager.updateStakeAndTotalStakes(params._borrower);

        // Re-insert Position in to the sorted list
        uint newNICR = _getNewNominalICRFromPositionChange(
            vars.coll, vars.debt, vars.collChange, vars.isCollIncrease, vars.netDebtChange, params._isDebtIncrease, collateral.asset.decimals()
        );

        collateral.sortedPositions.reInsert(params._borrower, newNICR, params._upperHint, params._lowerHint);

        emit PositionUpdated(params.asset, params.version, params._borrower, vars.newDebt, vars.newColl, vars.stake, uint8(PositionOperation.adjustPosition));
        emit StableBorrowingFeePaid(params.asset, params.version, params._borrower, vars.stableFee);

        // Use the unmodified _stableChange here, as we don't send the fee to the user
        _moveTokensAndCollateralFromAdjustment(collateral, stableToken, msg.sender, vars, params);
    }

    /**
     * @dev Closes an existing position
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function closePosition(address asset, uint8 version) external override {
        require(
            assetToVersionToUserToEscrow[asset][version][msg.sender].startTimestamp == 0,
            "Claim your escrowed stables before closing position"
        );

        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(asset, version);

        _requirePositionIsActive(collateral.positionManager, msg.sender);

        uint price = collateral.priceFeed.fetchLowestPrice(false, false);
        require(!collateralController.checkRecoveryMode(asset, collateral.version, price), "PositionController: Operation not permitted during Recovery Mode");

        collateral.positionManager.applyPendingRewards(msg.sender);

        uint coll = collateral.positionManager.getPositionColl(msg.sender);
        uint debt = collateral.positionManager.getPositionDebt(msg.sender);

        _requireSufficientStableBalance(stableToken, msg.sender, debt - GAS_COMPENSATION);

        uint newTCR = _getNewTCRFromPositionChange(collateral, coll, false, debt, false, price);
        _requireNewTCRisAboveCCR(newTCR, asset, version);

        collateral.positionManager.removeStake(msg.sender);
        collateral.positionManager.closePosition(msg.sender);

        emit PositionUpdated(asset, version, msg.sender, 0, 0, 0, uint8(PositionOperation.closePosition));

        // Burn the repaid stable from the user's balance and the gas compensation from the Gas Pool
        _repayStables(collateral.activePool, stableToken, msg.sender, debt - GAS_COMPENSATION);
        _repayStables(collateral.activePool, stableToken, gasPoolAddress, GAS_COMPENSATION);

        // Send the collateral back to the user
        collateral.activePool.sendCollateral(msg.sender, coll);
    }

    /**
     * @dev Allows users to claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function claimCollateral(address asset, uint8 version) external override {
        // send Collateral from CollSurplus Pool to owner
        collateralController.getCollateralInstance(asset, version).collateralSurplusPool.claimColl(msg.sender);
    }

    // --- Helper functions ---

    // ... (previous code remains unchanged)

    /**
     * @dev Triggers the borrowing fee calculation and distribution
     * @param _positionManager The position manager contract
     * @param _stableToken The stable token contract
     * @param _stableAmount The amount of stable tokens being borrowed
     * @param _maxFeePercentage The maximum fee percentage allowed by the user
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated stable fee
     */
    function _triggerBorrowingFee(
        IPositionManager _positionManager,
        IStable _stableToken,
        uint _stableAmount,
        uint _maxFeePercentage,
        uint suggestedAdditiveFeePCT
    ) internal returns (uint) {
        _positionManager.decayBaseRateFromBorrowing(); // decay the baseRate state variable
        uint stableFee = _positionManager.getBorrowingFee(_stableAmount, suggestedAdditiveFeePCT);
        _requireUserAcceptsFee(stableFee, _stableAmount, _maxFeePercentage);

        // Send fee to feetoken staking contract
        feeTokenStaking.increaseF_STABLE(stableFee);
        _stableToken.mint(feeTokenStakingAddress, stableFee);

        return stableFee;
    }

    /**
     * @dev Calculates the USD value of the collateral
     * @param _coll The amount of collateral
     * @param _price The price of the collateral
     * @return The USD value of the collateral
     */
    function _getUSDValue(uint _coll, uint _price) internal pure returns (uint) {
        uint usdValue = (_price * _coll) / DECIMAL_PRECISION;
        return usdValue;
    }

    /**
     * @dev Determines the collateral change amount and direction
     * @param _collReceived The amount of collateral received
     * @param _requestedCollWithdrawal The amount of collateral requested for withdrawal
     * @return collChange The amount of collateral change
     * @return isCollIncrease True if collateral is increasing, false if decreasing
     */
    function _getCollChange(
        uint _collReceived,
        uint _requestedCollWithdrawal
    )
    internal
    pure
    returns (uint collChange, bool isCollIncrease)
    {
        if (_collReceived != 0) {
            collChange = _collReceived;
            isCollIncrease = true;
        } else {
            collChange = _requestedCollWithdrawal;
        }
    }

    /**
     * @dev Updates a position's collateral and debt based on the adjustment
     * @param _positionManager The position manager contract
     * @param _borrower The address of the borrower
     * @param _collChange The amount of collateral change
     * @param _isCollIncrease True if collateral is increasing, false if decreasing
     * @param _debtChange The amount of debt change
     * @param _isDebtIncrease True if debt is increasing, false if decreasing
     * @return newColl The new collateral amount
     * @return newDebt The new debt amount
     */
    function _updatePositionFromAdjustment
    (
        IPositionManager _positionManager,
        address _borrower,
        uint _collChange,
        bool _isCollIncrease,
        uint _debtChange,
        bool _isDebtIncrease
    )
    internal
    returns (uint, uint)
    {
        uint newColl = (_isCollIncrease) ? _positionManager.increasePositionColl(_borrower, _collChange)
            : _positionManager.decreasePositionColl(_borrower, _collChange);
        uint newDebt = (_isDebtIncrease) ? _positionManager.increasePositionDebt(_borrower, _debtChange)
            : _positionManager.decreasePositionDebt(_borrower, _debtChange);

        return (newColl, newDebt);
    }

    /**
     * @dev Moves tokens and collateral based on the position adjustment
     * @param collateral The collateral struct
     * @param _stableToken The stable token contract
     * @param _borrower The address of the borrower
     * @param vars The local variables for position adjustment
     * @param params The parameters for position adjustment
     */
    function _moveTokensAndCollateralFromAdjustment
    (
        ICollateralController.Collateral memory collateral,
        IStable _stableToken,
        address _borrower,
        LocalVariables_adjustPosition memory vars,
        AdjustPositionParams memory params
    )
    internal
    {
        if (params._isDebtIncrease) {
            (uint requiredEscrowDuration,) = collateralController.getLoanCooldownRequirement(address(collateral.asset), collateral.version);
            bool shouldEscrow = requiredEscrowDuration != 0;
            _withdrawStable(collateral.activePool, _stableToken, _borrower, params._stableChange, vars.netDebtChange, shouldEscrow, address(collateral.asset), collateral.version, vars.price);
        } else {
            _repayStables(collateral.activePool, _stableToken, _borrower, params._stableChange);
        }

        if (vars.isCollIncrease) {
            _activePoolAddColl(collateral, vars.collChange);
        } else {
            collateral.activePool.sendCollateral(_borrower, vars.collChange);
        }
    }

    /**
     * @dev Adds collateral to the Active Pool
     * @param collateral The collateral struct
     * @param _amount The amount of collateral to add
     */
    function _activePoolAddColl(ICollateralController.Collateral memory collateral, uint _amount) internal {
        collateral.asset.safeTransferFrom(msg.sender, address(collateral.activePool), _amount);
        collateral.activePool.receiveCollateral(address(collateral.asset), _amount);
    }

    /**
     * @dev Withdraws stable tokens and updates the active debt
     * @param _activePool The active pool contract
     * @param _stableToken The stable token contract
     * @param _account The account to receive the stable tokens
     * @param _stableAmount The amount of stable tokens to withdraw
     * @param _netDebtIncrease The net increase in debt
     * @param shouldEscrow Whether the withdrawn amount should be escrowed
     * @param asset The address of the collateral asset
     * @param version The version of the collateral
     * @param quotePrice The current price quote
     */
    function _withdrawStable(
        IActivePool _activePool,
        IStable _stableToken,
        address _account,
        uint _stableAmount,
        uint _netDebtIncrease,
        bool shouldEscrow,
        address asset,
        uint8 version,
        uint quotePrice
    ) internal {
        _activePool.increaseStableDebt(_netDebtIncrease);
        if (shouldEscrow) {
            require(
                assetToVersionToUserToEscrow[asset][version][_account].startTimestamp == 0,
                "Claim your escrowed stables before creating more debt"
            );

            LoanOriginationEscrow memory loe =
                            LoanOriginationEscrow(_account, asset, version, block.timestamp, _stableAmount, quotePrice, 0);

            assetToVersionToUserToEscrow[asset][version][_account] = loe;
        } else {
            _stableToken.mint(_account, _stableAmount);
        }
    }

    /**
     * @dev Repays stable tokens and decreases the active debt
     * @param _activePool The active pool contract
     * @param _stableToken The stable token contract
     * @param _account The account repaying the stable tokens
     * @param _stables The amount of stable tokens to repay
     */
    function _repayStables(IActivePool _activePool, IStable _stableToken, address _account, uint _stables) internal {
        _activePool.decreaseStableDebt(_stables);
        _stableToken.burn(_account, _stables);
    }

    /**
     * @dev Ensures that only one type of collateral change (withdrawal or addition) is performed
     * @param _collWithdrawal The amount of collateral withdrawal
     * @param _collAddition The amount of collateral addition
     */
    function _requireSingularCollChange(uint _collWithdrawal, uint _collAddition) internal pure {
        require(_collAddition == 0 || _collWithdrawal == 0, "PositionController: Cannot withdraw and add coll");
    }

    /**
     * @dev Ensures that at least one type of adjustment (collateral or debt) is being made
     * @param _collWithdrawal The amount of collateral withdrawal
     * @param _stableChange The amount of stable token change
     * @param _collAddition The amount of collateral addition
     */
    function _requireNonZeroAdjustment(uint _collWithdrawal, uint _stableChange, uint _collAddition) internal pure {
        require(
            _collAddition != 0 || _collWithdrawal != 0 || _stableChange != 0,
            "PositionController: There must be either a collateral change or a debt change"
        );
    }

    /**
     * @dev Ensures that the position is active
     * @param _positionManager The position manager contract
     * @param _borrower The address of the borrower
     */
    function _requirePositionIsActive(IPositionManager _positionManager, address _borrower) internal view {
        uint status = _positionManager.getPositionStatus(_borrower);
        require(status == 1, "PositionController: Position does not exist or is closed");
    }

    /**
     * @dev Ensures that the position is not active
     * @param _positionManager The position manager contract
     * @param _borrower The address of the borrower
     */
    function _requirePositionIsNotActive(IPositionManager _positionManager, address _borrower) internal view {
        uint status = _positionManager.getPositionStatus(_borrower);
        require(status != 1, "PositionController: Position is active");
    }

    /**
     * @dev Ensures that the debt change is non-zero
     * @param _stableChange The amount of stable token change
     */
    function _requireNonZeroDebtChange(uint _stableChange) internal pure {
        require(_stableChange > 0, "PositionController: Debt increase requires non-zero debtChange");
    }

    /**
     * @dev Ensures that no collateral withdrawal is performed in Recovery Mode
     * @param _collWithdrawal The amount of collateral withdrawal
     */
    function _requireNoCollWithdrawal(uint _collWithdrawal) internal pure {
        require(_collWithdrawal == 0, "PositionController: Collateral withdrawal not permitted Recovery Mode");
    }

    /**
     * @dev Validates the position adjustment based on the current mode (Normal or Recovery)
     * @param collateral The collateral struct
     * @param _isRecoveryMode Whether the system is in Recovery Mode
     * @param _collWithdrawal The amount of collateral withdrawal
     * @param _isDebtIncrease Whether the debt is increasing
     * @param _vars The local variables for position adjustment
     */
    function _requireValidAdjustmentInCurrentMode(
        ICollateralController.Collateral memory collateral,
        bool _isRecoveryMode,
        uint _collWithdrawal,
        bool _isDebtIncrease,
        LocalVariables_adjustPosition memory _vars
    )
    internal
    view
    {
        /*
        *In Recovery Mode, only allow:
        *
        * - Pure collateral top-up
        * - Pure debt repayment
        * - Collateral top-up with debt repayment
        * - A debt increase combined with a collateral top-up which makes the ICR >= 150% and improves the ICR (and by extension improves the TCR).
        *
        * In Normal Mode, ensure:
        *
        * - The new ICR is above MCR
        * - The adjustment won't pull the TCR below CCR
        */
        if (_isRecoveryMode) {
            _requireNoCollWithdrawal(_collWithdrawal);
            if (_isDebtIncrease) {
                _requireICRisAboveCCR(_vars.newICR, address(collateral.asset), collateral.version);
                _requireNewICRisAboveOldICR(_vars.newICR, _vars.oldICR);
            }
        } else { // if Normal Mode
            _requireICRisAboveMCR(_vars.newICR, address(collateral.asset), collateral.version);
            _vars.newTCR = _getNewTCRFromPositionChange(collateral, _vars.collChange, _vars.isCollIncrease, _vars.netDebtChange, _isDebtIncrease, _vars.price);
            _requireNewTCRisAboveCCR(_vars.newTCR, address(collateral.asset), collateral.version);
        }
    }

    /**
     * @dev Ensures that the new ICR is above the Minimum Collateralization Ratio (MCR)
     * @param _newICR The new Individual Collateralization Ratio
     * @param asset The address of the collateral asset
     * @param version The version of the collateral
     */
    function _requireICRisAboveMCR(uint _newICR, address asset, uint8 version) internal view {
        require(
            _newICR >= collateralController.getMCR(asset, version),
            "PositionController: An operation that would result in ICR < MCR is not permitted"
        );
    }

    /**
     * @dev Ensures that the new ICR is above the Critical Collateralization Ratio (CCR)
     * @param _newICR The new Individual Collateralization Ratio
     * @param asset The address of the collateral asset
     * @param version The version of the collateral
     */
    function _requireICRisAboveCCR(uint _newICR, address asset, uint8 version) internal view {
        require(
            _newICR >= collateralController.getCCR(asset, version),
            "PositionController: Operation must leave position with ICR >= CCR"
        );
    }

    /**
     * @dev Ensures that the new ICR is above the old ICR in Recovery Mode
     * @param _newICR The new Individual Collateralization Ratio
     * @param _oldICR The old Individual Collateralization Ratio
     */
    function _requireNewICRisAboveOldICR(uint _newICR, uint _oldICR) internal pure {
        require(_newICR >= _oldICR, "PositionController: Cannot decrease your Position's ICR in Recovery Mode");
    }

    /**
     * @dev Ensures that the new TCR is above the Critical Collateralization Ratio (CCR)
     * @param _newTCR The new Total Collateralization Ratio
     * @param asset The address of the collateral asset
     * @param version The version of the collateral
     */
    function _requireNewTCRisAboveCCR(uint _newTCR, address asset, uint8 version) internal view {
        require(
            _newTCR >= collateralController.getCCR(asset, version),
            "PositionController: An operation that would result in TCR < CCR is not permitted"
        );
    }

    /**
     * @dev Ensures that the net debt is at least the minimum allowed
     * @param _netDebt The net debt amount
     */
    function _requireAtLeastMinNetDebt(uint _netDebt) internal pure {
        require(_netDebt >= MIN_NET_DEBT, "PositionController: Position's net debt must be greater than minimum");
    }

    /**
     * @dev Validates that the stable repayment amount is valid
     * @param _currentDebt The current debt of the position
     * @param _debtRepayment The amount of debt to be repaid
     */
    function _requireValidStableRepayment(uint _currentDebt, uint _debtRepayment) internal pure {
        require(_debtRepayment <= (_currentDebt - GAS_COMPENSATION), "PositionController: Amount repaid must not be larger than the Position's debt");
    }

    /**
     * @dev Ensures that the caller is the Backstop Pool
     */
    function _requireCallerIsBackstopPool() internal view {
        require(msg.sender == backstopPoolAddress, "PositionController: Caller is not Backstop Pool");
    }

    /**
     * @dev Checks if the borrower has sufficient stable balance for repayment
     * @param _stableToken The stable token contract
     * @param _borrower The address of the borrower
     * @param _debtRepayment The amount of debt to be repaid
     */
    function _requireSufficientStableBalance(IStable _stableToken, address _borrower, uint _debtRepayment) internal view {
        require(_stableToken.balanceOf(_borrower) >= _debtRepayment, "PositionController: Caller doesn't have enough stable to make repayment");
    }

    /**
     * @dev Validates the maximum fee percentage based on the current mode and suggested fee
     * @param _maxFeePercentage The maximum fee percentage specified by the user
     * @param _isRecoveryMode Whether the system is in Recovery Mode
     * @param asset The address of the collateral asset
     * @param version The version of the collateral
     * @param suggestedFeePCT The suggested fee percentage
     */
    function _requireValidMaxFeePercentage(uint _maxFeePercentage, bool _isRecoveryMode, address asset, uint8 version, uint suggestedFeePCT) internal view {
        uint minBorrowingFeePct = collateralController.getMinBorrowingFeePct(asset, version);

        if (_isRecoveryMode) {
            require(_maxFeePercentage <= DECIMAL_PRECISION, "Max fee percentage must less than or equal to 100%");
        } else {
            uint floor = StableMath._max(DYNAMIC_BORROWING_FEE_FLOOR, minBorrowingFeePct) + suggestedFeePCT;
            bool effectiveFeeAccepted = _maxFeePercentage >= floor && _maxFeePercentage <= DECIMAL_PRECISION;
            require(effectiveFeeAccepted, "Max fee percentage must be between 0.5% and 100%");
        }
    }

    /**
     * @dev Computes the new nominal ICR (Individual Collateralization Ratio) after a position change
     * @param _coll Current collateral amount
     * @param _debt Current debt amount
     * @param _collChange Amount of collateral change
     * @param _isCollIncrease True if collateral is increasing, false if decreasing
     * @param _debtChange Amount of debt change
     * @param _isDebtIncrease True if debt is increasing, false if decreasing
     * @param decimals Decimals of the collateral asset
     * @return The new nominal ICR
     */
    function _getNewNominalICRFromPositionChange
    (
        uint _coll,
        uint _debt,
        uint _collChange,
        bool _isCollIncrease,
        uint _debtChange,
        bool _isDebtIncrease,
        uint8 decimals
    )
    pure
    internal
    returns (uint)
    {
        (uint newColl, uint newDebt) = _getNewPositionAmounts(_coll, _debt, _collChange, _isCollIncrease, _debtChange, _isDebtIncrease);
        return StableMath._computeNominalCR(newColl, newDebt, decimals);
    }

    /**
     * @dev Computes the new ICR (Individual Collateralization Ratio) after a position change
     * @param _coll Current collateral amount
     * @param _debt Current debt amount
     * @param _collChange Amount of collateral change
     * @param _isCollIncrease True if collateral is increasing, false if decreasing
     * @param _debtChange Amount of debt change
     * @param _isDebtIncrease True if debt is increasing, false if decreasing
     * @param _price Current price of the collateral
     * @param _collateralDecimals Decimals of the collateral asset
     * @return The new ICR
     */
    function _getNewICRFromPositionChange
    (
        uint _coll,
        uint _debt,
        uint _collChange,
        bool _isCollIncrease,
        uint _debtChange,
        bool _isDebtIncrease,
        uint _price,
        uint8 _collateralDecimals
    )
    pure
    internal
    returns (uint)
    {
        (uint newColl, uint newDebt) = _getNewPositionAmounts(_coll, _debt, _collChange, _isCollIncrease, _debtChange, _isDebtIncrease);
        return StableMath._computeCR(newColl, newDebt, _price, _collateralDecimals);
    }

    /**
     * @dev Calculates new collateral and debt amounts after a position change
     * @param _coll Current collateral amount
     * @param _debt Current debt amount
     * @param _collChange Amount of collateral change
     * @param _isCollIncrease True if collateral is increasing, false if decreasing
     * @param _debtChange Amount of debt change
     * @param _isDebtIncrease True if debt is increasing, false if decreasing
     * @return New collateral amount and new debt amount
     */
    function _getNewPositionAmounts(
        uint _coll,
        uint _debt,
        uint _collChange,
        bool _isCollIncrease,
        uint _debtChange,
        bool _isDebtIncrease
    )
    internal
    pure
    returns (uint, uint)
    {
        uint newColl = _coll;
        uint newDebt = _debt;

        newColl = _isCollIncrease ? _coll + _collChange : _coll - _collChange;
        newDebt = _isDebtIncrease ? _debt + _debtChange : _debt - _debtChange;

        return (newColl, newDebt);
    }

    /**
     * @dev Calculates the new TCR (Total Collateralization Ratio) after a position change
     * @param collateral Struct containing collateral information
     * @param _collChange Amount of collateral change
     * @param _isCollIncrease True if collateral is increasing, false if decreasing
     * @param _debtChange Amount of debt change
     * @param _isDebtIncrease True if debt is increasing, false if decreasing
     * @param _price Current price of the collateral
     * @return The new TCR
     */
    function _getNewTCRFromPositionChange
    (
        ICollateralController.Collateral memory collateral,
        uint _collChange,
        bool _isCollIncrease,
        uint _debtChange,
        bool _isDebtIncrease,
        uint _price
    )
    internal
    view
    returns (uint)
    {
        uint totalColl = collateralController.getAssetColl(address(collateral.asset), collateral.version);
        uint totalDebt = collateralController.getAssetDebt(address(collateral.asset), collateral.version);

        totalColl = _isCollIncrease ? totalColl + _collChange : totalColl - _collChange;
        totalDebt = _isDebtIncrease ? totalDebt + _debtChange : totalDebt - _debtChange;

        uint newTCR = StableMath._computeCR(totalColl, totalDebt, _price, collateral.asset.decimals());
        return newTCR;
    }

    /**
     * @dev Ensures that the new debt does not exceed the debt cap for the collateral
     * @param collateral Struct containing collateral information
     * @param _debtChange Amount of debt change
     */
    function _requireDoesNotExceedCap(ICollateralController.Collateral memory collateral, uint _debtChange) internal view {
        uint totalDebt = collateralController.getAssetDebt(address(collateral.asset), collateral.version);
        require(
            totalDebt + _debtChange <= collateralController.getDebtCap(address(collateral.asset), collateral.version),
            "PositionController: Debt would exceed current debt cap"
        );
    }

    /**
     * @dev Ensures that the collateral is not in the process of being decommissioned
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireNotSunsetting(address asset, uint8 version) internal view {
        require(!collateralController.isDecommissioned(asset, version), "PositionController: Collateral is sunsetting");
    }

    /**
     * @dev Calculates the composite debt (user debt + gas compensation)
     * @param _debt User debt amount
     * @return Composite debt amount
     */
    function getCompositeDebt(uint _debt) external view override returns (uint) {
        return _debt + GAS_COMPENSATION;
    }
}
Stable.sol 156 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "../interfaces/ICollateralController.sol";
import "../interfaces/IStable.sol";

/**
 * @title USDx Stable
 * @dev Implementation of a stablecoin token with additional security features and controlled minting/burning.
 * This contract extends ERC20Permit for gasless approvals and includes ReentrancyGuard for security.
 */
contract Stable is IStable, ERC20Permit {
    // Immutable addresses for key components of the system
    address public immutable backstopPoolAddress;
    address public immutable positionControllerAddress;
    ICollateralController public immutable collateralController;

    /**
     * @dev Constructor to set up the Stable token
     * @param _collateralController Address of the CollateralController contract
     * @param _backstopPoolAddress Address of the BackstopPool contract
     * @param _positionControllerAddress Address of the PositionController contract
     */
    constructor(
        address _collateralController,
        address _backstopPoolAddress,
        address _positionControllerAddress
    ) ERC20("USDx", "USDx") ERC20Permit("USDx") {
        collateralController = ICollateralController(_collateralController);
        backstopPoolAddress = _backstopPoolAddress;
        positionControllerAddress = _positionControllerAddress;
    }

    // Modifiers for access control

    modifier onlyPositionController() {
        require(msg.sender == positionControllerAddress, "Stable: Caller is not PositionController");
        _;
    }

    modifier onlyBackstopPool() {
        require(msg.sender == backstopPoolAddress, "Stable: Caller is not the BackstopPool");
        _;
    }

    modifier onlyPositionManagerOrBackstopPool() {
        require(
            collateralController.validPositionManager(msg.sender) || msg.sender == backstopPoolAddress,
            "Stable: Caller is neither PositionManager nor BackstopPool"
        );
        _;
    }

    modifier onlyPositionManager() {
        require(
            collateralController.validPositionManager(msg.sender),
            "Stable: Caller is not a valid PositionManager"
        );
        _;
    }

    /**
     * @dev Mints new tokens. Can only be called by the PositionController.
     * @param _account Address to receive the minted tokens
     * @param _amount Amount of tokens to mint
     */
    function mint(address _account, uint256 _amount) external onlyPositionController {
        _mint(_account, _amount);
    }

    /**
     * @dev Burns tokens. Can be called by PositionController, PositionManager, or BackstopPool.
     * @param _account Address from which to burn tokens
     * @param _amount Amount of tokens to burn
     */
    function burn(address _account, uint256 _amount) external {
        require(
            msg.sender == positionControllerAddress ||
            collateralController.validPositionManager(msg.sender) ||
            msg.sender == backstopPoolAddress,
            "Stable: Caller is neither PositionController nor PositionManager nor BackstopPool"
        );
        _burn(_account, _amount);
    }

    /**
     * @dev Transfers tokens for redemption escrow. Can only be called by a PositionManager.
     * @param _sender Address sending the tokens
     * @param _positionManager Address of the PositionManager (recipient)
     * @param _amount Amount of tokens to transfer
     */
    function transferForRedemptionEscrow(address _sender, address _positionManager, uint256 _amount) external override onlyPositionManager {
        _transfer(_sender, _positionManager, _amount);
    }

    /**
     * @dev Sends tokens to a pool. Can only be called by the BackstopPool.
     * @param _sender Address sending the tokens
     * @param _poolAddress Address of the pool (recipient)
     * @param _amount Amount of tokens to send
     */
    function sendToPool(address _sender, address _poolAddress, uint256 _amount) external onlyBackstopPool {
        _transfer(_sender, _poolAddress, _amount);
    }

    /**
     * @dev Returns tokens from a pool. Can be called by PositionManager or BackstopPool.
     * @param _poolAddress Address of the pool sending the tokens
     * @param _receiver Address receiving the tokens
     * @param _amount Amount of tokens to return
     */
    function returnFromPool(address _poolAddress, address _receiver, uint256 _amount) external onlyPositionManagerOrBackstopPool {
        _transfer(_poolAddress, _receiver, _amount);
    }

    /**
     * @dev Overrides the standard ERC20 transfer function with additional checks and updates
     * @param recipient Address receiving the tokens
     * @param amount Amount of tokens to transfer
     * @return success Boolean indicating whether the transfer was successful
     */
    function transfer(address recipient, uint256 amount) public override(IERC20, ERC20) returns (bool) {
        _requireValidRecipient(recipient);
        return super.transfer(recipient, amount);
    }

    /**
     * @dev Overrides the standard ERC20 transferFrom function with additional checks and updates
     * @param sender Address sending the tokens
     * @param recipient Address receiving the tokens
     * @param amount Amount of tokens to transfer
     * @return success Boolean indicating whether the transfer was successful
     */
    function transferFrom(address sender, address recipient, uint256 amount) public override(IERC20, ERC20) returns (bool) {
        _requireValidRecipient(recipient);
        return super.transferFrom(sender, recipient, amount);
    }

    /**
     * @dev Internal function to check if a recipient is valid for token transfers
     * @param _recipient Address of the recipient
     */
    function _requireValidRecipient(address _recipient) internal view {
        require(
            _recipient != address(0) &&
            _recipient != address(this),
            "Stable: Cannot transfer tokens directly to the Stable token contract or the zero address"
        );
        require(
            _recipient != backstopPoolAddress &&
            _recipient != positionControllerAddress,
            "Stable: Cannot transfer tokens directly to the BackstopPool or PositionController"
        );
    }
}
WeightedRedemptionManager.sol 885 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../interfaces/ICollateralController.sol";
import "../interfaces/IPositionManager.sol";
import "../interfaces/IStable.sol";
import "../interfaces/IPriceFeed.sol";
import "../interfaces/IActivePool.sol";
import "../interfaces/IDefaultPool.sol";
import "./PermissionedRedeemerAbstract.sol";

/**
 * @title WeightedRedemptionManager
 * @notice Manages weighted redemptions with same interface as PositionManager
 * @dev Implements escrow internally, assumes attached PMs have escrow disabled
 */
contract WeightedRedemptionManager is PermissionedRedeemerAbstract, Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    // Core contracts
    ICollateralController public immutable collateralController;
    IStable public immutable stableToken;
    address public immutable feeRecipient;

    // Weight management (collateral token => version => weight)
    mapping(address => mapping(uint8 => uint256)) public collateralWeights;

    // Active status
    bool public isActive;

    // Escrow system - matches PositionManager structure
    struct EscrowedRedemption {
        uint256 stableAmount;
        uint256 timestamp;
        // Track allocated amounts per collateral/version for proportional redemptions
        mapping(address => mapping(uint8 => uint256)) allocations;
    }

    mapping(address => EscrowedRedemption) public escrowedRedemptions;

    // Redemption parameters for an individual collateral subsystem
    struct CollateralRedemptionParams {
        address collateral;
        uint8 version;
        uint256 stableAmount;
        address firstRedemptionHint;
        address upperPartialRedemptionHint;
        address lowerPartialRedemptionHint;
        uint256 partialRedemptionHintNICR;
        uint256 maxIterations;
        uint256 maxFeePercentage;
    }

    // Cooldown settings
    uint256 public redemptionCooldownPeriod;
    uint256 public redemptionGracePeriod;
    uint256 public redemptionsTimeoutFeePct;

    // Redemption points system
    uint256 public maxRedemptionPoints;
    uint256 public availableRedemptionPoints;
    uint256 public redemptionRegenerationRate; // points per minute
    uint256 public lastRedemptionRegenerationTimestamp;

    // Minimum redemption size (to prevent dust redemptions)
    uint256 public minStableRedemptionSize;

    // Events
    event WeightSet(
        address indexed collateral,
        uint8 indexed version,
        uint256 weight
    );

    event RedemptionQueued(
        address indexed user,
        uint256 stableAmount,
        uint256 timestamp,
        uint256 totalAllocated
    );

    event RedemptionExecuted(
        address indexed user,
        uint256 totalRequested,
        uint256 totalActuallyRedeemed,
        uint256 remainingEscrow
    );

    event RedemptionTimedOut(
        address indexed user,
        uint256 escrowAmount,
        uint256 timeoutFee,
        uint256 returnAmount,
        address feeRecipient
    );

    event CollateralRedemptionSuccess(
        address indexed user,
        address indexed collateral,
        uint8 indexed version,
        uint256 requested,
        uint256 actuallyRedeemed,
        uint256 collateralReceived
    );

    event CollateralRedemptionFailed(
        address indexed user,
        address indexed collateral,
        uint8 indexed version,
        uint256 attemptedAmount
    );

    event EmergencyDequeueExecuted(
        address indexed user,
        address indexed collateral,
        uint8 indexed version,
        uint256 stableAmount,
        bool tcrBelowMCR,
        bool inactiveTimeout
    );

    event RedemptionPointsConsumed(
        address indexed user,
        uint256 amountConsumed,
        uint256 pointsBefore,
        uint256 pointsAfter,
        uint256 workingPointsBeforeConsumption,
        uint256 lastRegenTimestamp,
        uint256 currentTimestamp,
        uint256 regenRate,
        uint256 maxPoints
    );

    event ActiveStatusChanged(bool isActive);
    event CooldownPeriodSet(uint256 cooldownPeriod);
    event GracePeriodSet(uint256 gracePeriod);
    event TimeoutFeeSet(uint256 timeoutFeePct);
    event MinRedemptionSizeSet(uint256 minSize);
    event MaxRedemptionPointsSet(uint256 maxPoints);
    event RedemptionRegenerationRateSet(uint256 rate);
    event AvailableRedemptionPointsSet(uint256 points);

    // Errors
    error OnlyOwnerWhenInactive();
    error NotAuthorizedToRedeem();
    error AmountMustBeGreaterThanZero();
    error AmountBelowMinimumRedemptionSize();
    error RedemptionAlreadyQueued();
    error CooldownRequirementMustBeGreaterThanZero();
    error NoWeightedCollateralsConfigured();
    error NoRedemptionAmountCalculated();
    error RedemptionAmountExceedsCap();
    error RedemptionNotQueued();
    error RedemptionCooldownNotSatisfied();
    error CannotRedeemMoreThanQueued();
    error AmountExceedsAllocationForCollateral();
    error NoRedemptionQueued();
    error NoAllocationForThisCollateral();
    error EmergencyDequeueNotAllowed();
    error RegenerationRateMustBeLessThanOrEqualMaxRedemptionPoints();
    error RegenRateMustBe60AndAboveOrZero();
    error AvailableRedemptionPointsMustBeLessThanOrEqualMaxRedemptionPoints();
    error TimeoutFeeMustBeFivePercentOrLess();

    constructor(
        address _collateralController,
        address _stableToken,
        address _feeRecipient
    ) {
        collateralController = ICollateralController(_collateralController);
        stableToken = IStable(_stableToken);
        feeRecipient = _feeRecipient;

        redemptionCooldownPeriod = 10 minutes;
        redemptionGracePeriod = 5 minutes;
        redemptionsTimeoutFeePct = 5e15; // 0.5%
        maxRedemptionPoints = type(uint).max;
        availableRedemptionPoints = type(uint).max; // Start with max points
        redemptionRegenerationRate = 0;
        lastRedemptionRegenerationTimestamp = block.timestamp;
        minStableRedemptionSize = 100e18; // 100 min size by default
        isActive = false; // Start as inactive - only owner can use until activated for testing purposes
    }

    /**
     * @notice Get user's redemption state including all allocations
     * @param user The user address
     * @return stableAmount Total stables in escrow
     * @return timestamp When redemption was queued
     * @return collaterals Array of all weighted collaterals
     * @return versions Array of collateral versions
     * @return amounts Array of allocated amounts (0 if no allocation)
     */
    function getUserRedemptionState(address user) external view returns (
        uint256 stableAmount,
        uint256 timestamp,
        address[] memory collaterals,
        uint8[] memory versions,
        uint256[] memory amounts
    ) {
        EscrowedRedemption storage redemption = escrowedRedemptions[user];
        stableAmount = redemption.stableAmount;
        timestamp = redemption.timestamp;

        // Get all active collaterals
        ICollateralController.Collateral[] memory activeCollaterals = collateralController.getActiveCollaterals();
        uint256 length = activeCollaterals.length;

        collaterals = new address[](length);
        versions = new uint8[](length);
        amounts = new uint256[](length);

        // Iterate and populate whatever allocations exist
        for (uint i = 0; i < length; i++) {
            address collateral = address(activeCollaterals[i].asset);
            uint8 version = activeCollaterals[i].version;

            collaterals[i] = collateral;
            versions[i] = version;
            amounts[i] = redemption.allocations[collateral][version];
        }
    }

    /**
     * @notice Get total weight across all active collaterals
     * @return The total weight sum
     */
    function totalWeight() public view returns (uint256) {
        ICollateralController.Collateral[] memory activeCollaterals = collateralController.getActiveCollaterals();
        uint256 length = activeCollaterals.length;
        uint256 total = 0;

        for (uint i = 0; i < length; i++) {
            address collateral = address(activeCollaterals[i].asset);
            uint8 version = activeCollaterals[i].version;
            total += collateralWeights[collateral][version];
        }

        return total;
    }

    /**
     * @notice Get all configured weights for active collaterals
     * @return collaterals Array of collateral addresses
     * @return versions Array of collateral versions
     * @return weights Array of weights for each collateral
     * @return totalWeightSum The total weight across all collaterals
     */
    function getWeights() external view returns (
        address[] memory collaterals,
        uint8[] memory versions,
        uint256[] memory weights,
        uint256 totalWeightSum
    ) {
        ICollateralController.Collateral[] memory activeCollaterals = collateralController.getActiveCollaterals();
        uint256 length = activeCollaterals.length;

        collaterals = new address[](length);
        versions = new uint8[](length);
        weights = new uint256[](length);

        for (uint i = 0; i < length; i++) {
            address collateral = address(activeCollaterals[i].asset);
            uint8 version = activeCollaterals[i].version;

            collaterals[i] = collateral;
            versions[i] = version;
            weights[i] = collateralWeights[collateral][version];
            totalWeightSum += collateralWeights[collateral][version];
        }
    }

    // ======================== PositionManager Interface Functions ========================

    /**
     * @notice Queue a redemption request (matches PositionManager interface)
     * @param _stableAmount The amount of stable tokens to queue for redemption
     */
    function queueRedemption(uint _stableAmount) external {
        _validateRedemptionRequest(_stableAmount);

        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];

        // Calculate allocations across weighted collaterals
        uint256 totalAllocated = _calculateAndStoreAllocations(_stableAmount, redemption);
        if (totalAllocated < minStableRedemptionSize) revert AmountBelowMinimumRedemptionSize();

        // Consume redemption points for the actual allocated amount
        _regenerateAndConsumeRedemptionPoints(totalAllocated);

        // Transfer only the allocated amount from user to this contract
        IERC20(address(stableToken)).safeTransferFrom(msg.sender, address(this), totalAllocated);

        // Timestamp, total amount...
        _storeRedemptionMetadata(redemption, totalAllocated);

        emit RedemptionQueued(msg.sender, _stableAmount, block.timestamp, totalAllocated);
    }

    /**
     * @notice Validates that a redemption request meets all requirements
     * @param _stableAmount The requested redemption amount
     */
    function _validateRedemptionRequest(uint256 _stableAmount) private view {
        // Check if non-owner trying to use while inactive
        if (!isActive && msg.sender != owner()) revert OnlyOwnerWhenInactive();

        // Check redemption permissions (owner can always redeem)
        if (msg.sender != owner() && !canRedeem(msg.sender)) revert NotAuthorizedToRedeem();

        // Validate amount
        if (_stableAmount == 0) revert AmountMustBeGreaterThanZero();
        if (_stableAmount < minStableRedemptionSize) revert AmountBelowMinimumRedemptionSize();

        // Check no existing redemption queued
        if (escrowedRedemptions[msg.sender].timestamp != 0) revert RedemptionAlreadyQueued();

        // Check system configuration
        if (redemptionCooldownPeriod == 0) revert CooldownRequirementMustBeGreaterThanZero();
        if (totalWeight() == 0) revert NoWeightedCollateralsConfigured();
    }

    /**
     * @notice Calculates and stores allocations for each weighted collateral
     * @param _stableAmount The amount to allocate proportionally
     * @param redemption The redemption storage to update
     * @return totalAllocated The total amount allocated across all collaterals
     */
    function _calculateAndStoreAllocations(
        uint256 _stableAmount,
        EscrowedRedemption storage redemption
    ) private returns (uint256 totalAllocated) {
        ICollateralController.Collateral[] memory activeCollaterals = collateralController.getActiveCollaterals();

        uint256 length = activeCollaterals.length;
        bool[] memory isBelowMCR = new bool[](length);
        uint256 adjustedTotalWeight = 0;

        // First pass: Calculate total weight from active collaterals and cache TCR check results
        for (uint256 i = 0; i < length; i++) {
            ICollateralController.Collateral memory c = activeCollaterals[i];
            address collateral = address(c.asset);
            uint8 version = c.version;
            uint256 weight = collateralWeights[collateral][version];

            isBelowMCR[i] = _isTCRBelowMCR(collateral, version);
            if (!isBelowMCR[i]) {
                adjustedTotalWeight += weight;
            }
        }

        // Second pass: allocate to eligible collaterals proportionally
        for (uint256 i = 0; i < length; i++) {
            // Skip collaterals with TCR < MCR (using cached result)
            if (isBelowMCR[i]) {
                continue;
            }

            ICollateralController.Collateral memory c = activeCollaterals[i];
            address collateral = address(c.asset);
            uint8 version = c.version;
            uint256 weight = collateralWeights[collateral][version];

            if (weight > 0) {
                // Calculate proportional allocation
                uint256 allocation = (_stableAmount * weight) / adjustedTotalWeight;
                redemption.allocations[collateral][version] = allocation;
                totalAllocated += allocation;
            }
        }

        // Ensure we have something to redeem
        if (totalAllocated == 0) revert NoRedemptionAmountCalculated();

        return totalAllocated;
    }

    /**
     * @notice Stores the redemption metadata after successful validation and allocation
     * @param redemption The redemption storage to update
     * @param totalAllocated The total amount that was allocated
     */
    function _storeRedemptionMetadata(
        EscrowedRedemption storage redemption,
        uint256 totalAllocated
    ) private {
        redemption.stableAmount = totalAllocated;
        redemption.timestamp = block.timestamp;
    }

    /**
     * @notice Execute redemption with parameters for each collateral
     * @param redemptionParams Array of redemption parameters for each collateral
     */
    function redeemCollateral(CollateralRedemptionParams[] calldata redemptionParams) external {
        // Check if non-owner trying to use while inactive
        if (!isActive && msg.sender != owner()) revert OnlyOwnerWhenInactive();

        // Get user's redemption
        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];
        if (redemption.timestamp == 0) revert RedemptionNotQueued();

        // Check for timeout and handle if necessary
        if (_checkAndHandleTimeout(redemption)) {
            return;
        }

        // Validate redemption execution requirements
        _validateRedemptionExecution(redemption, redemptionParams);

        // Execute redemptions for each collateral
        uint256 totalRequested = 0;
        uint256 totalActuallyRedeemed = 0;

        for (uint i = 0; i < redemptionParams.length; i++) {
            CollateralRedemptionParams memory params = redemptionParams[i];

            if (params.stableAmount > 0) {
                totalRequested += params.stableAmount;
                totalActuallyRedeemed += _executeSingleCollateralRedemption(redemption, params);
            }
        }

        // Update escrow with results
        _updateRedemptionEscrow(redemption, totalActuallyRedeemed);

        // Emit redemption executed event
        emit RedemptionExecuted(msg.sender, totalRequested, totalActuallyRedeemed, redemption.stableAmount);
    }

    /**
     * @notice Checks if redemption has timed out and handles the timeout if necessary
     * @param redemption The redemption to check
     * @return isTimedOut True if the redemption was timed out and handled
     */
    function _checkAndHandleTimeout(EscrowedRedemption storage redemption) private returns (bool isTimedOut) {
        uint cooldownExpiry = redemption.timestamp + redemptionCooldownPeriod;
        uint gracePeriodExpiry = cooldownExpiry + redemptionGracePeriod;

        isTimedOut = block.timestamp >= gracePeriodExpiry;

        if (isTimedOut) {
            _processTimeoutRedemption(redemption);
        }

        return isTimedOut;
    }

    /**
     * @notice Clears all allocation entries and deletes the redemption escrow
     * @param redemption The redemption to clear and delete
     * @dev Required because Solidity's delete operator does not clear nested mappings.
     *      We must manually clear all allocations before calling delete.
     */
    function _clearAndDeleteRedemption(EscrowedRedemption storage redemption) private {
        ICollateralController.Collateral[] memory activeCollaterals = collateralController.getActiveCollaterals();
        uint256 length = activeCollaterals.length;

        for (uint256 i = 0; i < length; i++) {
            address collateral = address(activeCollaterals[i].asset);
            uint8 version = activeCollaterals[i].version;
            redemption.allocations[collateral][version] = 0;
        }

        delete escrowedRedemptions[msg.sender];
    }

    /**
     * @notice Processes a timed out redemption by charging fees and returning remaining funds
     * @param redemption The redemption to process
     */
    function _processTimeoutRedemption(EscrowedRedemption storage redemption) private {
        // Calculate timeout fee and return amount
        uint timeoutFee = (redemptionsTimeoutFeePct * redemption.stableAmount) / 1e18;
        uint returnAmount = redemption.stableAmount - timeoutFee;

        // Transfer back to user minus fee
        if (returnAmount > 0) {
            IERC20(address(stableToken)).safeTransfer(msg.sender, returnAmount);
        }

        // Send timeout fee to fee recipient
        if (timeoutFee > 0) {
            IERC20(address(stableToken)).safeTransfer(feeRecipient, timeoutFee);
        }

        // Emit timeout event
        emit RedemptionTimedOut(msg.sender, redemption.stableAmount, timeoutFee, returnAmount, feeRecipient);

        // Clear the escrow completely (clears nested mapping and deletes)
        _clearAndDeleteRedemption(redemption);
    }

    /**
     * @notice Validates that redemption execution meets all requirements
     * @param redemption The redemption being executed
     * @param redemptionParams The redemption parameters to validate
     */
    function _validateRedemptionExecution(
        EscrowedRedemption storage redemption,
        CollateralRedemptionParams[] calldata redemptionParams
    ) private view {
        // Check cooldown has passed
        uint cooldownExpiry = redemption.timestamp + redemptionCooldownPeriod;
        if (block.timestamp < cooldownExpiry) revert RedemptionCooldownNotSatisfied();

        // Calculate and validate total requested amount
        uint256 totalRequested = 0;
        for (uint i = 0; i < redemptionParams.length; i++) {
            totalRequested += redemptionParams[i].stableAmount;
        }

        if (totalRequested == 0) revert AmountMustBeGreaterThanZero();
        if (totalRequested > redemption.stableAmount) revert CannotRedeemMoreThanQueued();
    }

    /**
     * @notice Executes redemption for a single collateral
     * @param redemption The user's redemption storage
     * @param params The redemption parameters for this collateral
     * @return actuallyRedeemed The amount actually redeemed
     */
    function _executeSingleCollateralRedemption(
        EscrowedRedemption storage redemption,
        CollateralRedemptionParams memory params
    ) private returns (uint256 actuallyRedeemed) {
        // Check allocation exists
        uint256 allocation = redemption.allocations[params.collateral][params.version];
        if (params.stableAmount > allocation) revert AmountExceedsAllocationForCollateral();

        // Get the collateral instance
        ICollateralController.Collateral memory collateralInstance =
                            collateralController.getCollateralInstance(params.collateral, params.version);

        // Track stable balance before redemption
        uint256 stableBalanceBefore = IERC20(address(stableToken)).balanceOf(address(this));

        // Track collateral balance before redemption to measure what we receive
        uint256 collateralBalanceBefore = IERC20(params.collateral).balanceOf(address(this));

        // Execute redemption on position manager
        try collateralInstance.positionManager.redeemCollateral(
            params.stableAmount,
            params.firstRedemptionHint,
            params.upperPartialRedemptionHint,
            params.lowerPartialRedemptionHint,
            params.partialRedemptionHintNICR,
            params.maxIterations,
            params.maxFeePercentage
        ) {
            // Calculate actual amount consumed
            uint256 stableBalanceAfter = IERC20(address(stableToken)).balanceOf(address(this));
            actuallyRedeemed = stableBalanceBefore - stableBalanceAfter;

            // Calculate collateral received
            uint256 collateralBalanceAfter = IERC20(params.collateral).balanceOf(address(this));
            uint256 collateralReceived = collateralBalanceAfter - collateralBalanceBefore;

            // Forward the collateral to the user
            if (collateralReceived > 0) {
                IERC20(params.collateral).safeTransfer(msg.sender, collateralReceived);
            }

            // Update allocation with actual consumed amount
            redemption.allocations[params.collateral][params.version] -= actuallyRedeemed;

            // Emit success event with collateral amount
            emit CollateralRedemptionSuccess(
                msg.sender,
                params.collateral,
                params.version,
                params.stableAmount,
                actuallyRedeemed,
                collateralReceived
            );
        } catch {
            // If redemption fails, emit failure event
            // Stables remain in escrow, allocation unchanged
            emit CollateralRedemptionFailed(
                msg.sender,
                params.collateral,
                params.version,
                params.stableAmount
            );
            actuallyRedeemed = 0;
        }

        return actuallyRedeemed;
    }

    /**
     * @notice Updates the redemption escrow after execution
     * @param redemption The redemption to update
     * @param totalActuallyRedeemed The total amount that was redeemed
     */
    function _updateRedemptionEscrow(
        EscrowedRedemption storage redemption,
        uint256 totalActuallyRedeemed
    ) private {
        // Update escrow amounts with actual redemption
        redemption.stableAmount -= totalActuallyRedeemed;

        // Clear escrow if fully redeemed (clears nested mapping and deletes)
        if (redemption.stableAmount == 0) {
            _clearAndDeleteRedemption(redemption);
        }
    }

    /**
     * @notice Emergency dequeue for a specific position manager
     * @param collateral The collateral address to dequeue from
     * @param version The version of the collateral
     */
    function emergencyDequeue(address collateral, uint8 version) external {
        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];
        if (redemption.timestamp == 0) revert NoRedemptionQueued();

        uint256 allocation = redemption.allocations[collateral][version];
        if (allocation == 0) revert NoAllocationForThisCollateral();

        // Clear this specific allocation
        redemption.allocations[collateral][version] = 0;
        redemption.stableAmount -= allocation;

        // If all allocations are cleared, delete the entire redemption (clears nested mapping and deletes)
        if (redemption.stableAmount == 0) {
            _clearAndDeleteRedemption(redemption);
        }

        // Check if emergency dequeue is allowed: either TCR < MCR OR (isActive is false AND past grace period)
        bool tcrBelowMCR = _isTCRBelowMCR(collateral, version);
        bool inactiveAndPastGrace = false;

        if (!isActive) {
            uint256 cooldownEnd = redemption.timestamp + redemptionCooldownPeriod;
            inactiveAndPastGrace = block.timestamp >= cooldownEnd;
        }

        if (!tcrBelowMCR && !inactiveAndPastGrace) {
            revert EmergencyDequeueNotAllowed();
        }

        // Return the dequeued stables to user
        IERC20(address(stableToken)).safeTransfer(msg.sender, allocation);

        // Emit emergency dequeue event
        emit EmergencyDequeueExecuted(msg.sender, collateral, version, allocation, tcrBelowMCR, inactiveAndPastGrace);
    }

    /**
     * @notice Check if address has enqueued escrow (matches PositionManager interface)
     * @param redeemer The address to check
     * @return True if has enqueued redemption
     */
    function hasEnqueuedEscrow(address redeemer) external view returns (bool) {
        return escrowedRedemptions[redeemer].timestamp != 0;
    }

    // ======================== Internal Helper Functions ========================

    /**
     * @notice Check if TCR is below MCR for a given collateral
     * @dev fetchPrice() does not carry out any safety checks, but as it is not used as part of the main redemption loop,
     *      it's fine to use here. In the worst case of there being a broken Chainlink feed, a user would only be able to
     *      enter a loop of enqueue/emergencyDequeue.
     * @param collateral The collateral address
     * @param version The collateral version
     * @return True if TCR < MCR, false otherwise
     */
    function _isTCRBelowMCR(address collateral, uint8 version) private view returns (bool) {
        ICollateralController.Collateral memory instance =
                            collateralController.getCollateralInstance(collateral, version);

        uint256 TCR = instance.positionManager.getTCR(instance.priceFeed.fetchPrice(0).highestPrice);
        uint256 MCR = collateralController.getMCR(collateral, version);
        return TCR < MCR;
    }

    // ======================== Points System Internal Functions ========================

    /**
     * @notice Regenerates and consumes redemption points
     * @param amount Amount of redemption points to consume
     */
    function _regenerateAndConsumeRedemptionPoints(uint256 amount) internal {
        uint256 workingRedemptionPoints = _redemptionPointsAt(block.timestamp);
        if (amount > workingRedemptionPoints) revert RedemptionAmountExceedsCap();

        uint256 oldAvailablePoints = availableRedemptionPoints;
        uint256 oldTimestamp = lastRedemptionRegenerationTimestamp;

        availableRedemptionPoints = workingRedemptionPoints - amount;
        lastRedemptionRegenerationTimestamp = block.timestamp;

        // Emit comprehensive points consumption event with all raw values
        emit RedemptionPointsConsumed(
            msg.sender,
            amount,
            oldAvailablePoints,
            availableRedemptionPoints,
            workingRedemptionPoints,
            oldTimestamp,
            block.timestamp,
            redemptionRegenerationRate,
            maxRedemptionPoints
        );
    }

    /**
     * @notice Calculate redemption points at a specific timestamp
     * @param targetTimestamp The timestamp to calculate points for
     * @return workingRedemptionPoints The calculated points
     */
    function _redemptionPointsAt(uint256 targetTimestamp) internal view returns (uint256 workingRedemptionPoints) {
        workingRedemptionPoints = _calculatePointsAt(
            lastRedemptionRegenerationTimestamp,
            redemptionRegenerationRate,
            availableRedemptionPoints,
            maxRedemptionPoints,
            targetTimestamp
        );
    }

    /**
     * @notice Common function to calculate points based on regeneration
     * @dev Matches CollateralController's implementation exactly
     */
    function _calculatePointsAt(
        uint256 lastTimestamp,
        uint256 regenerationRatePerMin,
        uint256 availablePoints,
        uint256 maxPoints,
        uint256 targetTimestamp
    ) internal pure returns (uint256) {
        if (targetTimestamp <= lastTimestamp) {
            return availablePoints;
        }

        uint256 secondsElapsed = targetTimestamp - lastTimestamp;

        (bool safeMul, uint256 _regeneratedPoints) = secondsElapsed.tryMul(regenerationRatePerMin);
        uint256 regeneratedPoints = (safeMul ? _regeneratedPoints : type(uint256).max) / 60;

        (bool safeAdd, uint256 newPoints) = availablePoints.tryAdd(regeneratedPoints);
        uint256 safeAddPoints = safeAdd ? newPoints : type(uint256).max;

        bool isMaxRegeneration = safeAddPoints > maxPoints;
        return isMaxRegeneration ? maxPoints : safeAddPoints;
    }

    // ======================== View Functions ========================

    /**
     * @notice Get redemption cooldown requirements
     * @return cooldownPeriod The cooldown period
     * @return gracePeriod The grace period
     * @return timeoutFeePct The timeout fee percentage
     */
    function getRedemptionCooldownRequirement() external view returns (uint256, uint256, uint256) {
        return (
            redemptionCooldownPeriod,
            redemptionGracePeriod,
            redemptionsTimeoutFeePct
        );
    }

    /**
     * @notice Get current available redemption points
     * @return The current available redemption points
     */
    function getAvailableRedemptionPoints() external view returns (uint256) {
        return _redemptionPointsAt(block.timestamp);
    }

    /**
     * @notice Get redemption points at a specific timestamp
     * @param targetTimestamp Target timestamp for calculation
     * @return workingRedemptionPoints Calculated redemption points
     */
    function redemptionPointsAt(uint256 targetTimestamp) external view returns (uint256 workingRedemptionPoints) {
        return _redemptionPointsAt(targetTimestamp);
    }

    // ======================== Admin Functions ========================

    /**
     * @notice Set active status
     * @param _isActive Whether the contract should be active for all users
     */
    function setActive(bool _isActive) external onlyOwner {
        isActive = _isActive;
        emit ActiveStatusChanged(_isActive);
    }

    /**
     * @notice Set weight for a collateral
     */
    function setCollateralWeight(address collateral, uint8 version, uint256 weight) external onlyOwner {
        // Set the new weight
        collateralWeights[collateral][version] = weight;

        emit WeightSet(collateral, version, weight);
    }

    /**
     * @notice Set maximum redemption points
     */
    function setMaxRedemptionPoints(uint256 _maxPoints) external onlyOwner {
        if (redemptionRegenerationRate > _maxPoints) revert RegenerationRateMustBeLessThanOrEqualMaxRedemptionPoints();
        maxRedemptionPoints = _maxPoints;
        if (availableRedemptionPoints > maxRedemptionPoints) {
            availableRedemptionPoints = maxRedemptionPoints;
        }
        emit MaxRedemptionPointsSet(_maxPoints);
    }

    /**
     * @notice Set redemption regeneration rate
     * @param _regenerationRatePerMinute Points regenerated per minute
     */
    function setRedemptionRegenerationRate(uint256 _regenerationRatePerMinute) external onlyOwner {
        if (_regenerationRatePerMinute != 0 && _regenerationRatePerMinute < 60) {
            revert RegenRateMustBe60AndAboveOrZero();
        }
        if (_regenerationRatePerMinute > maxRedemptionPoints) {
            revert RegenerationRateMustBeLessThanOrEqualMaxRedemptionPoints();
        }

        // Calculate points with old rate and update
        uint256 regeneratedWithOldSetting = _redemptionPointsAt(block.timestamp);
        availableRedemptionPoints = regeneratedWithOldSetting;
        lastRedemptionRegenerationTimestamp = block.timestamp;
        redemptionRegenerationRate = _regenerationRatePerMinute;
        emit RedemptionRegenerationRateSet(_regenerationRatePerMinute);
    }

    /**
     * @notice Set available redemption points directly
     */
    function setAvailableRedemptionPoints(uint256 _availablePoints) external onlyOwner {
        if (_availablePoints > maxRedemptionPoints) revert AvailableRedemptionPointsMustBeLessThanOrEqualMaxRedemptionPoints();
        availableRedemptionPoints = _availablePoints;
        emit AvailableRedemptionPointsSet(_availablePoints);
    }

    /**
     * @notice Set redemption cooldown period
     */
    function setRedemptionCooldown(uint256 _cooldownPeriod) external onlyOwner {
        if (_cooldownPeriod == 0) revert CooldownRequirementMustBeGreaterThanZero();
        redemptionCooldownPeriod = _cooldownPeriod;
        emit CooldownPeriodSet(_cooldownPeriod);
    }

    /**
     * @notice Set redemption grace period
     */
    function setRedemptionGracePeriod(uint256 _gracePeriod) external onlyOwner {
        redemptionGracePeriod = _gracePeriod;
        emit GracePeriodSet(_gracePeriod);
    }

    /**
     * @notice Set redemptions timeout fee percentage
     */
    function setRedemptionsTimeoutFeePct(uint256 _timeoutFeePct) external onlyOwner {
        if (_timeoutFeePct > 2e17) revert TimeoutFeeMustBeFivePercentOrLess(); // 20%
        redemptionsTimeoutFeePct = _timeoutFeePct;
        emit TimeoutFeeSet(_timeoutFeePct);
    }

    /**
     * @notice Set minimum stable redemption size to prevent dust redemptions
     * @param _minSize The minimum amount of stable tokens required for redemption
     */
    function setMinStableRedemptionSize(uint256 _minSize) external onlyOwner {
        minStableRedemptionSize = _minSize;
        emit MinRedemptionSizeSet(_minSize);
    }
}
CollateralController.sol 472 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "../../common/StableMath.sol";
import "../../interfaces/ICollateralController.sol";
import "../../Guardable.sol";
import "../BaseRateAbstract.sol";
import "./CollateralControllerState.sol";
import "./CollateralControllerImpl.sol";
import "@openzeppelin/contracts/governance/TimelockController.sol";
import "./NonPayableProxy.sol";

/**
 * @title CollateralController
 * @dev Contract for managing collateral settings, redemptions, and fees across different asset versions.
 *
 * Key features:
 *      1. Collateral Management: Supports multiple collateral types and versions.
 *      2. Fee Control: Manages borrowing and redemption fees.
 *      3. Redemption System: Implements a point-based redemption system with cooldowns.
 *      4. Loan Point System: Manages loan points for controlling borrowing capacity.
 *      5. Recovery Mode: Implements checks for system recovery mode.
 *      6. Decommissioning: Allows for graceful sunsetting of collateral types.
 *      7. Security Measures: Includes commissioning periods and various safety checks.
 */
contract CollateralController
    is CollateralControllerState,
       NonPayableProxy,
       Guardable,
       TimelockController {
    // !!! IMPORTANT !!!: Do not reorder inheritance.
    // CollateralControllerState must start at storage slot zero, so that shared state with CollateralControllerImpl is aligned.

    using EnumerableSet for EnumerableSet.AddressSet;
    using Address for address;

    /// @dev implementation contract for lifecycle management timelocked ops
    address public impl;

    /**
     * @dev Sets the position controller address
     * @param pc Address of the position controller
     * Can only be called by the guardian.
     * This function is contained here rather than in the CollateralControllerImpl contract,
     * as it's somewhat administrative in nature rather than being about collateral management.
     */
    function setPositionController(address pc) external onlyGuardian {
        require(positionControllerAddress == address(0));
        positionControllerAddress = pc;
    }

    /**
     * @dev Constructor sets collateral lifecycle/settings impl and initial proposers/executors.
     *      Until sufficient distribution of FeeToken, default timelock admin can add/remove proposers.
     *
     * @notice The minDelay cannot be changed unilaterally, even by the defaultAdmin
     */
    constructor(uint256 minDelay, address[] memory proposers, address[] memory executors) TimelockController(
        minDelay,   // min delay between proposal and execution
        proposers,  // proposers can submit transaction bundles to the timelock
        executors,  // executors can trigger the action after minDelay
        msg.sender  // default timelock admin. Can assign and remove roles. But can't change minDelay.
                    // Revoked in favour of Governor contract eventually
    )  {
        impl = address(new CollateralControllerImpl());
    }

    function _implementation() internal view override returns (address) {
        return address(impl);
    }

    //==================================================================//
    //----------------------- COLLATERAL MGMT --------------------------//
    //------------------------ TIMELOCK OPS ----------------------------//
    //==================================================================//

    modifier onlyTimelock {
        require(msg.sender == address(this), "CollateralController: caller must be timelock");
        _;
    }

    modifier onlyTimelockOrGuardian {
        require(msg.sender == address(this) || msg.sender == guardian(), "CollateralController: caller must be timelock or guardian");
        _;
    }

    /**
    * @dev Supports a new collateral type
     * @param collateralAddress Address of the collateral token
     * @param positionManagerAddress Address of the position manager
     * @param sortedPositionsAddress Address of the sorted positions contract
     * @param activePoolAddress Address of the active pool
     * @param priceFeedAddress Address of the price feed
     * @param defaultPoolAddress Address of the default pool
     * @param collateralSurplusPoolAddress Address of the collateral surplus pool
     * Can only be called by the guardian
     */
    function supportCollateral(
        address collateralAddress,
        address positionManagerAddress,
        address sortedPositionsAddress,
        address activePoolAddress,
        address priceFeedAddress,
        address defaultPoolAddress,
        address collateralSurplusPoolAddress
    ) external onlyTimelock {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "supportCollateral(address,address,address,address,address,address,address)",
                collateralAddress,
                positionManagerAddress,
                sortedPositionsAddress,
                activePoolAddress,
                priceFeedAddress,
                defaultPoolAddress,
                collateralSurplusPoolAddress
            ),
            "CollateralController: supportCollateral delegatecall failed"
        );
    }

    /**
     * @dev Activates a collateral after its commissioning period
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * Can only be called by the guardian
     */
    function commissionToActive(address asset, uint8 version) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "commissionToActive(address,uint8)",
                asset,
                version
            ),
            "CollateralController: commissionToActive delegatecall failed"
        );
    }

    /**
     * @dev Decommissions a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * Can only be called by the guardian
     */
    function decommission(address asset, uint8 version) external onlyTimelock {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "decommission(address,uint8)",
                asset,
                version
            ),
            "CollateralController: decommission delegatecall failed"
        );
    }

    /**
     * @dev Moves a collateral from active to sunset state
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * Can only be called by the guardian
     */
    function activeToSunset(address asset, uint8 version) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "activeToSunset(address,uint8)",
                asset,
                version
            ),
            "CollateralController: activeToSunset delegatecall failed"
        );
    }

    /**
     * @dev Sets the maximum loan points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param maxPoints New maximum loan points
     * Can only be called by the guardian
     */
    function setMaxLoanPoints(address asset, uint8 version, uint maxPoints) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setMaxLoanPoints(address,uint8,uint256)",
                asset,
                version,
                maxPoints
            ),
            "CollateralController: setMaxLoanPoints delegatecall failed"
        );
    }

    /**
     * @dev Sets the loan regeneration rate for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param regenPointsPerMinute New regeneration rate in points per minute
     * Can only be called by the guardian
     */
    function setLoanRegenerationRate(address asset, uint8 version, uint regenPointsPerMinute) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setLoanRegenerationRate(address,uint8,uint256)",
                asset,
                version,
                regenPointsPerMinute
            ),
            "CollateralController: setLoanRegenerationRate delegatecall failed"
        );
    }

    /**
     * @dev Sets the available loan points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newAvailableLoanPoints New available loan points
     * Can only be called by the guardian
     */
    function setAvailableLoanPoints(address asset, uint8 version, uint newAvailableLoanPoints) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setAvailableLoanPoints(address,uint8,uint256)",
                asset,
                version,
                newAvailableLoanPoints
            ),
            "CollateralController: setAvailableLoanPoints delegatecall failed"
        );
    }

    /**
     * @dev Sets the debt cap for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newDebtCap New debt cap value
     * Can only be called by the guardian
     */
    function setDebtCap(address asset, uint8 version, uint256 newDebtCap) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setDebtCap(address,uint8,uint256)",
                asset,
                version,
                newDebtCap
            ),
            "CollateralController: setDebtCap delegatecall failed"
        );
    }

    /**
     * @dev Sets the base rate type for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param brt New base rate type
     * Can only be called by the guardian
     */
    function setBaseRateType(address asset, uint8 version, ICollateralController.BaseRateType brt) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setBaseRateType(address,uint8,uint8)",
                asset,
                version,
                uint8(brt)
            ),
            "CollateralController: setBaseRateType delegatecall failed"
        );
    }

    /**
     * @dev Sets fee settings for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _minRedemptionsFeePct Minimum redemptions fee percentage
     * @param _maxRedemptionsFeePct Maximum redemptions fee percentage
     * @param _minBorrowingFeePct Minimum borrowing fee percentage
     * @param _maxBorrowingFeePct Maximum borrowing fee percentage
     * @param _redemptionsTimeoutFeePct Redemptions timeout fee percentage
     * Can only be called by the guardian
     */
    function setFeeSettings(address asset, uint8 version,
        uint _minRedemptionsFeePct,
        uint _maxRedemptionsFeePct,
        uint _minBorrowingFeePct,
        uint _maxBorrowingFeePct,
        uint _redemptionsTimeoutFeePct
    ) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setFeeSettings(address,uint8,uint256,uint256,uint256,uint256,uint256)",
                asset,
                version,
                _minRedemptionsFeePct,
                _maxRedemptionsFeePct,
                _minBorrowingFeePct,
                _maxBorrowingFeePct,
                _redemptionsTimeoutFeePct
            ),
            "CollateralController: setFeeSettings delegatecall failed"
        );
    }

    /**
     * @dev Sets collateral requirements for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param _MCR Minimum Collateralization Ratio
     * @param _CCR Critical Collateralization Ratio
     * Can only be called by the guardian
     */
    function setCollateralRequirements(address asset, uint8 version,
        uint _MCR,
        uint _CCR
    ) external onlyTimelock {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setCollateralRequirements(address,uint8,uint256,uint256)",
                asset,
                version,
                _MCR,
                _CCR
            ),
            "CollateralController: setCollateralRequirements delegatecall failed"
        );
    }

    /**
     * @dev Sets the redemption cooldown for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newRedemptionCooldownRequirement New redemption cooldown requirement
     * Can only be called by the guardian
     */
    function setRedemptionCooldown(address asset, uint8 version, uint newRedemptionCooldownRequirement) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setRedemptionCooldown(address,uint8,uint256)",
                asset,
                version,
                newRedemptionCooldownRequirement
            ),
            "CollateralController: setRedemptionCooldown delegatecall failed"
        );
    }

    /**
     * @dev Sets the redemption grace period for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newPeriod New grace period
     * Can only be called by the guardian
     */
    function setRedemptionGracePeriod(address asset, uint8 version, uint newPeriod) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setRedemptionGracePeriod(address,uint8,uint256)",
                asset,
                version,
                newPeriod
            ),
            "CollateralController: setRedemptionGracePeriod delegatecall failed"
        );
    }

    /**
     * @dev Sets the loan grace period for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newPeriod New grace period
     * Can only be called by the guardian
     */
    function setLoanGracePeriod(address asset, uint8 version, uint newPeriod) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setLoanGracePeriod(address,uint8,uint256)",
                asset,
                version,
                newPeriod
            ),
            "CollateralController: setLoanGracePeriod delegatecall failed"
        );
    }

    /**
     * @dev Sets the loan cooldown period for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newPeriod New cooldown period
     * Can only be called by the guardian
     */
    function setLoanCooldownPeriod(address asset, uint8 version, uint newPeriod) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setLoanCooldownPeriod(address,uint8,uint256)",
                asset,
                version,
                newPeriod
            ),
            "CollateralController: setLoanCooldownPeriod delegatecall failed"
        );
    }

    /**
     * @dev Sets the maximum redemption points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param maxPoints New maximum redemption points
     * Can only be called by the guardian
     */
    function setMaxRedemptionPoints(address asset, uint8 version, uint maxPoints) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setMaxRedemptionPoints(address,uint8,uint256)",
                asset,
                version,
                maxPoints
            ),
            "CollateralController: setMaxRedemptionPoints delegatecall failed"
        );
    }

    /**
     * @dev Sets the regeneration rate for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param regenPointsPerMinute New regeneration rate in points per minute
     * Can only be called by the guardian
     */
    function setRegenerationRate(address asset, uint8 version, uint regenPointsPerMinute) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setRegenerationRate(address,uint8,uint256)",
                asset,
                version,
                regenPointsPerMinute
            ),
            "CollateralController: setRegenerationRate delegatecall failed"
        );
    }

    /**
     * @dev Sets the available redemption points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newAvailableRedemptionPoints New available redemption points
     * Can only be called by the guardian
     */
    function setAvailableRedemptionPoints(address asset, uint8 version, uint newAvailableRedemptionPoints) external onlyTimelockOrGuardian {
        impl.functionDelegateCall(
            abi.encodeWithSignature(
                "setAvailableRedemptionPoints(address,uint8,uint256)",
                asset,
                version,
                newAvailableRedemptionPoints
            ),
            "CollateralController: setAvailableRedemptionPoints delegatecall failed"
        );
    }

    /**
     * @dev Exclude asset/version from backstop withdrawal validation check
     * This may be needed if any users deliberately leave debts open in a collateral with only one position.
     * @param asset The asset to exclude
     * @param version The particular version
     * @param excluded If true, subsystem will not be check for undercollateralised positions
     */
    function setBackstopWithdrawalValidation(address asset, uint8 version, bool excluded) external onlyTimelockOrGuardian {
        excludedFromBackstopWithdrawalChecks[asset][version] = excluded;
    }
}
CollateralControllerImpl.sol 903 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../../common/StableMath.sol";
import "../../interfaces/ICollateralController.sol";
import "./CollateralControllerState.sol";
import "../../Guardable.sol";

/**
 * @title CollateralControllerImpl
 * @dev Factored out functionality to reduce contract size of CollateralController.
 *      See CollateralController for docs.
 */
contract CollateralControllerImpl is CollateralControllerState, ICollateralController, Guardable {
    using EnumerableSet for EnumerableSet.AddressSet;

    /**
     * @dev Returns the guardian address
     * @return address The guardian address
     */
    function getGuardian() external override view returns (address) {
        return guardian();
    }

    /**
    * @dev Supports a new collateral type
     * @param collateralAddress Address of the collateral token
     * @param positionManagerAddress Address of the position manager
     * @param sortedPositionsAddress Address of the sorted positions contract
     * @param activePoolAddress Address of the active pool
     * @param priceFeedAddress Address of the price feed
     * @param defaultPoolAddress Address of the default pool
     * @param collateralSurplusPoolAddress Address of the collateral surplus pool
     */
    function supportCollateral(
        address collateralAddress,
        address positionManagerAddress,
        address sortedPositionsAddress,
        address activePoolAddress,
        address priceFeedAddress,
        address defaultPoolAddress,
        address collateralSurplusPoolAddress
    ) external {
        _requireAndSetUniqueDependencies(positionManagerAddress, sortedPositionsAddress, activePoolAddress,
            priceFeedAddress, defaultPoolAddress, collateralSurplusPoolAddress);

        _requireNotPreviouslyCompletelyRemoved(collateralAddress);

        uint8 version = _incrementVersion(collateralAddress);
        collateralToVersionToTimeOfCommission[collateralAddress][version] = block.timestamp;
        collateralToVersionToArrayIndex[collateralAddress][version] = collaterals.length;
        positionManagerToVersion[positionManagerAddress] = version;
        positionManagerToAsset[positionManagerAddress] = collateralAddress;

        //Push empty struct to avoid stack-too-deep error
        collaterals.push();
        ICollateralController.Collateral storage newCollateral = collaterals[collaterals.length - 1];
        ICollateralController.Settings storage s = collateralToVersionToSettings[collateralAddress][version];
        s.debtCap = 0;
        s.decommissionedOn = 0;
        s.MCR = MINIMUM_MCR;
        s.CCR = MINIMUM_CCR;

        s.redemptionSettings.redemptionCooldownPeriod = 0;
        s.redemptionSettings.redemptionGracePeriod = 0;
        s.redemptionSettings.maxRedemptionPoints = type(uint).max;
        s.redemptionSettings.availableRedemptionPoints = type(uint).max;
        s.redemptionSettings.redemptionRegenerationRate = 0;
        s.redemptionSettings.lastRedemptionRegenerationTimestamp = block.timestamp;

        s.loanSettings.loanCooldownPeriod = 0;
        s.loanSettings.loanGracePeriod = 0;
        s.loanSettings.maxLoanPoints = type(uint).max;
        s.loanSettings.availableLoanPoints = type(uint).max;
        s.loanSettings.loanRegenerationRate = 0;
        s.loanSettings.lastLoanRegenerationTimestamp = block.timestamp;

        s.feeSettings.baseRateType = ICollateralController.BaseRateType.Local;
        s.feeSettings.redemptionsTimeoutFeePct = DECIMAL_PRECISION / 1000 * 5; // 0.5%;
        s.feeSettings.minRedemptionsFeePct = DYNAMIC_REDEMPTION_FEE_FLOOR;
        s.feeSettings.maxRedemptionsFeePct = DECIMAL_PRECISION;
        s.feeSettings.minBorrowingFeePct = DYNAMIC_BORROWING_FEE_FLOOR;
        s.feeSettings.maxBorrowingFeePct = DECIMAL_PRECISION / 100 * 5; // 5%;

        newCollateral.version = version;
        newCollateral.asset = IERC20Metadata(collateralAddress);
        newCollateral.positionManager = IPositionManager(positionManagerAddress);
        newCollateral.sortedPositions = ISortedPositions(sortedPositionsAddress);
        newCollateral.activePool = IActivePool(activePoolAddress);
        newCollateral.priceFeed = IPriceFeed(priceFeedAddress);
        newCollateral.defaultPool = IDefaultPool(defaultPoolAddress);
        newCollateral.collateralSurplusPool = ICollateralSurplusPool(collateralSurplusPoolAddress);

        require(newCollateral.asset.decimals() == 18, "Collateral must have 18 decimals");
    }

    function commissionToActive(address asset, uint8 version) external {
        _requireVersionExists(asset, version);
        _requireNotPreviouslyCompletelyRemoved(asset);
        require(!_isDecommissioned(asset, version), "this asset has previously been decommissioned");
        _requireAfterCommissioningPeriod(asset, version);
        uniqueActiveCollaterals.add(asset);
        activeVersions[asset]++;
        require(!_isActive(asset, version), "Collateral is already active");
        activeCollaterals.push(collaterals[collateralToVersionToArrayIndex[asset][version]]);
        activeCollateralToVersionToArrayIndex[asset][version] = activeCollaterals.length - 1;
    }

    function decommission(address asset, uint8 version) external {
        // If we already decommissioned, then no need to do it again
        _requireValidAndActiveVersion(asset, version);
        _setDecommissioned(asset, version);
    }

    function activeToSunset(address asset, uint8 version) external {
        _requireVersionExists(asset, version);
        ICollateralController.Collateral memory c = _unsafeIndexCollateral(asset, version);
        require(!_commissioning(c), "This collateral is currently commissioning");
        _wasNotPreviouslyActive(asset, version);
        require(_decommissionedAndSunsetPeriodElapsed(c), "The decommissioning period must be respected");

        // remove escrow requirement, and allow queued stables in position manager to dequeue
        _setRedemptionCooldownPeriod(asset, version, 0);

        if (activeVersions[asset] <= 1) {
            uniqueActiveCollaterals.remove(asset);
            wasPreviouslyRemoved[asset] = true;
        }

        activeVersions[asset]--;
        collaterals[collateralToVersionToArrayIndex[asset][version]].sunset = true;

        uint indexToRemove = activeCollateralToVersionToArrayIndex[asset][version];
        uint activeCollateralsLength = activeCollaterals.length;

        if (indexToRemove == activeCollateralsLength - 1) {
            activeCollaterals.pop();
        } else {
            ICollateralController.Collateral memory replacement = activeCollaterals[activeCollateralsLength - 1];
            activeCollaterals[indexToRemove] = replacement;
            activeCollateralToVersionToArrayIndex[address(replacement.asset)][replacement.version] = indexToRemove;
            activeCollaterals.pop();
        }
    }

    function setMaxLoanPoints(address asset, uint8 version, uint maxPoints) external {
        _requireActiveOrSunsetting(asset, version);
        _setMaxLoanPoints(asset, version, maxPoints);
    }

    function setLoanRegenerationRate(address asset, uint8 version, uint regenPointsPerMinute) external {
        _requireActiveOrSunsetting(asset, version);
        _setLoanRegenerationRate(asset, version, regenPointsPerMinute);
    }

    function setAvailableLoanPoints(address asset, uint8 version, uint newAvailableLoanPoints) external {
        _requireActiveOrSunsetting(asset, version);
        _setAvailableLoanPoints(asset, version, newAvailableLoanPoints);
    }

    function setDebtCap(address asset, uint8 version, uint256 newDebtCap) external {
        // Not allowed to set a debt cap if decommissioned, as debt cap becomes zero implicitly
        _requireValidAndActiveVersion(asset, version);
        _setDebtCap(asset, version, newDebtCap);
    }

    function setBaseRateType(address asset, uint8 version, ICollateralController.BaseRateType brt) external {
        _requireValidAndActiveVersion(asset, version);
        ICollateralController.FeeSettings storage f = collateralToVersionToSettings[asset][version].feeSettings;
        f.baseRateType = brt;
    }

    function setFeeSettings(address asset, uint8 version,
        uint _minRedemptionsFeePct,
        uint _maxRedemptionsFeePct,
        uint _minBorrowingFeePct,
        uint _maxBorrowingFeePct,
        uint _redemptionsTimeoutFeePct
    ) external {
        _requireActiveOrSunsetting(asset, version);
        require(_minRedemptionsFeePct >= DYNAMIC_REDEMPTION_FEE_FLOOR, "invalid min redemption fee");
        require(_minBorrowingFeePct >= DYNAMIC_BORROWING_FEE_FLOOR, "invalid min borrowing fee");

        require(_maxBorrowingFeePct <= DECIMAL_PRECISION, "invalid _maxBorrowingFeePct");
        require(_maxRedemptionsFeePct <= DECIMAL_PRECISION, "invalid _maxRedemptionsFeePct");

        require(_minBorrowingFeePct <= _maxBorrowingFeePct, "invalid min borrowing fee");
        require(_minRedemptionsFeePct <= _maxRedemptionsFeePct, "invalid minRedemptionFeePct redemption fee");

        require(_redemptionsTimeoutFeePct <= DECIMAL_PRECISION / 100 * 5, "_redemptionsTimeoutFeePct"); // 5%

        ICollateralController.FeeSettings storage f = collateralToVersionToSettings[asset][version].feeSettings;
        f.minRedemptionsFeePct = _minRedemptionsFeePct;
        f.maxRedemptionsFeePct = _maxRedemptionsFeePct;
        f.minBorrowingFeePct = _minBorrowingFeePct;
        f.maxBorrowingFeePct = _maxBorrowingFeePct;
        f.redemptionsTimeoutFeePct = _redemptionsTimeoutFeePct;
    }

    function setCollateralRequirements(address asset, uint8 version,
        uint _MCR,
        uint _CCR
    ) external {
        require(_CCR > _MCR, "CCR must be greater than MCR");
        require(_MCR >= MINIMUM_MCR, "MCR must be greater than or equal to 110%");
        require(_CCR >= MINIMUM_CCR, "CCR must be greater than or equal to 150%");
        _requireActiveOrSunsetting(asset, version);
        ICollateralController.Settings storage s = collateralToVersionToSettings[asset][version];
        s.MCR = _MCR;
        s.CCR = _CCR;
    }

    function setRedemptionCooldown(address asset, uint8 version, uint newRedemptionCooldownRequirement) external {
        _requireActiveOrSunsetting(asset, version);
        _setRedemptionCooldownPeriod(asset, version, newRedemptionCooldownRequirement);
    }

    function setRedemptionGracePeriod(address asset, uint8 version, uint newPeriod) external {
        _requireActiveOrSunsetting(asset, version);
        ICollateralController.RedemptionSettings storage r = collateralToVersionToSettings[asset][version].redemptionSettings;
        r.redemptionGracePeriod = newPeriod;
    }

    function setLoanGracePeriod(address asset, uint8 version, uint newPeriod) external {
        _requireActiveOrSunsetting(asset, version);
        ICollateralController.LoanSettings storage l = collateralToVersionToSettings[asset][version].loanSettings;
        l.loanGracePeriod = newPeriod;
    }

    function setLoanCooldownPeriod(address asset, uint8 version, uint newPeriod) external {
        _requireActiveOrSunsetting(asset, version);
        ICollateralController.LoanSettings storage l = collateralToVersionToSettings[asset][version].loanSettings;
        l.loanCooldownPeriod = newPeriod;
    }

    function setMaxRedemptionPoints(address asset, uint8 version, uint maxPoints) external {
        _requireActiveOrSunsetting(asset, version);
        _setMaxRedemptionPoints(asset, version, maxPoints);
    }

    function setRegenerationRate(address asset, uint8 version, uint regenPointsPerMinute) external {
        _requireActiveOrSunsetting(asset, version);
        _setRedemptionRegenerationRate(asset, version, regenPointsPerMinute);
    }

    function setAvailableRedemptionPoints(address asset, uint8 version, uint newAvailableRedemptionPoints) external {
        _requireActiveOrSunsetting(asset, version);
        _setAvailableRedemptionPoints(asset, version, newAvailableRedemptionPoints);
    }

    function _incrementVersion(address asset) private returns (uint8 nextVersion) {
        latestVersion[asset]++;
        nextVersion = latestVersion[asset];
    }

    function _requireAndSetUniqueDependencies(
        address positionManagerAddress,
        address sortedPositionsAddress,
        address activePoolAddress,
        address priceFeedAddress,
        address defaultPoolAddress,
        address collateralSurplusPoolAddress
    ) private {
        require(!registered[positionManagerAddress], "positionManager is already set under a different deployment");
        require(!registered[sortedPositionsAddress], "sortedPositions is already set under a different deployment");
        require(!registered[activePoolAddress], "activePool is already set under a different deployment");
        require(!registered[priceFeedAddress], "priceFeed is already set under a different deployment");
        require(!registered[defaultPoolAddress], "defaultPool is already set under a different deployment");
        require(!registered[collateralSurplusPoolAddress], "collateralSurplusPool is already set under a different deployment");

        registered[positionManagerAddress] = true;
        registered[sortedPositionsAddress] = true;
        registered[activePoolAddress] = true;
        registered[priceFeedAddress] = true;
        registered[defaultPoolAddress] = true;
        registered[collateralSurplusPoolAddress] = true;
    }

    //==================================================================//
    //---------------------- Global Base Rate --------------------------//
    //==================================================================//

    /**
     * @dev Returns the last fee operation time
     * @return uint The timestamp of the last fee operation
     */
    function getLastFeeOperationTime() external view returns (uint) {
        return _lastFeeOperationTime;
    }

    /**
     * @dev Returns the current base rate
     * @return uint The current base rate
     */
    function getBaseRate() external view returns (uint) {
        return _baseRate;
    }

    /**
     * @dev Decays the base rate from borrowing
     * Can only be called by a valid position manager
     */
    function decayBaseRateFromBorrowing() external override {
        require(validPositionManager(msg.sender), "invalid PM");
        super._decayBaseRateFromBorrowing();
    }

    /**
     * @dev Updates the last fee operation time
     * Can only be called by a valid position manager
     */
    function updateLastFeeOpTime() external override {
        require(validPositionManager(msg.sender), "invalid PM");
        super._updateLastFeeOpTime();
    }

    /**
     * @dev Calculates minutes passed since last fee operation
     * @return uint Number of minutes passed
     * Can only be called by a valid position manager
     */
    function minutesPassedSinceLastFeeOp() external view override returns (uint) {
        require(validPositionManager(msg.sender), "invalid PM");
        return super._minutesPassedSinceLastFeeOp();
    }

    /**
     * @dev Calculates the decayed base rate
     * @return uint The decayed base rate
     * Can only be called by a valid position manager
     */
    function calcDecayedBaseRate() external view override returns (uint) {
        require(validPositionManager(msg.sender), "invalid PM");
        return super._calcDecayedBaseRate();
    }

    /**
     * @dev Updates the base rate from redemption
     * @param _CollateralDrawn Amount of collateral drawn
     * @param _price Current price
     * @param _totalStableSupply Total stable supply
     * @return uint Updated base rate
     * Can only be called by a valid position manager
     */
    function updateBaseRateFromRedemption(uint _CollateralDrawn, uint _price, uint _totalStableSupply)
    external override returns (uint) {
        require(validPositionManager(msg.sender), "invalid PM");
        return super._updateBaseRateFromRedemption(_CollateralDrawn, _price, _totalStableSupply);
    }

    //==================================================================//
    //------------------------ Rate Limiters ---------------------------//
    //==================================================================//

    /**
     * @dev Regenerates and consumes loan points
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param amount Amount of loan points to consume
     * @return utilizationPCT Utilization percentage after consumption
     * @return loadIncrease Increase in load
     */
    function regenerateAndConsumeLoanPoints(address asset, uint8 version, uint amount) external override returns (uint utilizationPCT, uint loadIncrease){
        require(msg.sender == positionControllerAddress, "Only callable by PC");
        uint workingLoanPoints = _loanPointsAt(asset, version, block.timestamp);
        require(amount <= workingLoanPoints, "Loan amount exceeds rate cap");
        Settings storage s = collateralToVersionToSettings[asset][version];

        uint existingUtilizationPCT =
                        _calculateUtilizationPercent(s.loanSettings.availableLoanPoints, s.loanSettings.maxLoanPoints);

        s.loanSettings.availableLoanPoints = workingLoanPoints - amount;
        s.loanSettings.lastLoanRegenerationTimestamp = block.timestamp;
        utilizationPCT = _calculateUtilizationPercent(s.loanSettings.availableLoanPoints, s.loanSettings.maxLoanPoints);
        loadIncrease = utilizationPCT > existingUtilizationPCT ? utilizationPCT - existingUtilizationPCT : 0;
    }

    /**
     * @dev Calculates the loan points at a specific timestamp
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param targetTimestamp Target timestamp for calculation
     * @return workingLoanPoints Calculated loan points
     */
    function loanPointsAt(
        address asset,
        uint8 version,
        uint targetTimestamp
    ) public override view returns (uint workingLoanPoints) {
        return _loanPointsAt(asset, version, targetTimestamp);
    }

    /**
     * @dev Regenerates and consumes redemption points
     * @param amount Amount of redemption points to consume
     * @return utilizationPCT Utilization percentage after consumption
     * @return loadIncrease Increase in load
     */
    function regenerateAndConsumeRedemptionPoints(uint amount) external override returns (uint utilizationPCT, uint loadIncrease) {
        uint8 version = getVersion(msg.sender);
        address asset = getAsset(msg.sender);
        Collateral storage instance = collaterals[collateralToVersionToArrayIndex[asset][version]];
        require(msg.sender == address(instance.positionManager), "Only callable by associated PM");
        uint workingRedemptionPoints = _redemptionPointsAt(asset, version, block.timestamp);
        require(amount <= workingRedemptionPoints, "Redemption amount exceeds cap");
        Settings storage s = collateralToVersionToSettings[asset][version];

        uint existingUtilizationPCT =
                        _calculateUtilizationPercent(s.redemptionSettings.availableRedemptionPoints, s.redemptionSettings.maxRedemptionPoints);

        s.redemptionSettings.availableRedemptionPoints = workingRedemptionPoints - amount;
        s.redemptionSettings.lastRedemptionRegenerationTimestamp = block.timestamp;
        utilizationPCT = _calculateUtilizationPercent(s.redemptionSettings.availableRedemptionPoints, s.redemptionSettings.maxRedemptionPoints);
        loadIncrease = utilizationPCT > existingUtilizationPCT ? utilizationPCT - existingUtilizationPCT : 0;
    }

    /**
     * @dev Calculates the redemption points at a specific timestamp
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param targetTimestamp Target timestamp for calculation
     * @return workingRedemptionPoints Calculated redemption points
     */
    function redemptionPointsAt(
        address asset,
        uint8 version,
        uint targetTimestamp
    ) public override view returns (uint workingRedemptionPoints) {
        return _redemptionPointsAt(asset, version, targetTimestamp);
    }

    /**
     * @dev Gets the redemption cooldown requirement
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Redemption cooldown period, grace period, and timeout fee percentage
     */
    function getRedemptionCooldownRequirement(address asset, uint8 version) external override view returns (uint, uint, uint) {
        return (
            collateralToVersionToSettings[asset][version].redemptionSettings.redemptionCooldownPeriod,
            collateralToVersionToSettings[asset][version].redemptionSettings.redemptionGracePeriod,
            collateralToVersionToSettings[asset][version].feeSettings.redemptionsTimeoutFeePct
        );
    }

    /**
     * @dev Gets the loan cooldown requirement
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Loan cooldown period and grace period
     */
    function getLoanCooldownRequirement(address asset, uint8 version) external override view returns (uint, uint) {
        return (
            collateralToVersionToSettings[asset][version].loanSettings.loanCooldownPeriod,
            collateralToVersionToSettings[asset][version].loanSettings.loanGracePeriod
        );
    }

    //==================================================================//
    //---------------------- COLLATERAL VIEWS --------------------------//
    //==================================================================//

    /**
     * @dev Gets the total collateral for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return assetColl Total collateral amount
     */
    function getAssetColl(address asset, uint8 version) external override view returns (uint assetColl) {
        assetColl = _unsafeIndexCollateral(asset, version).positionManager.getEntireCollateral();
    }

    /**
     * @dev Gets the total debt for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return assetDebt Total debt amount
     */
    function getAssetDebt(address asset, uint8 version) external view returns (uint assetDebt) {
        assetDebt = _unsafeIndexCollateral(asset, version).positionManager.getEntireDebt();
    }

    /**
     * @dev Calculates the borrowing rate
     * @param asset Address of the collateral asset
     * @param baseRate Base rate for calculation
     * @param suggestedAdditiveFeePCT Suggested additive fee percentage
     * @return Calculated borrowing rate
     */
    function calcBorrowingRate(address asset, uint baseRate, uint suggestedAdditiveFeePCT) external view override returns (uint) {
        uint8 version = getVersion(msg.sender);
        uint256 minBorrowingFeePct = getMinBorrowingFeePct(asset, version);
        uint256 maxBorrowingFeePct = getMaxBorrowingFeePct(asset, version);

        uint effectiveBaseRate =
            collateralToVersionToSettings[asset][version].feeSettings.baseRateType == BaseRateType.Local ?
                baseRate : _baseRate;

        return StableMath._min(
            StableMath._max(minBorrowingFeePct, DYNAMIC_BORROWING_FEE_FLOOR) + effectiveBaseRate + suggestedAdditiveFeePCT,
            maxBorrowingFeePct
        );
    }

    /**
     * @dev Calculates the redemption rate
     * @param asset Address of the collateral asset
     * @param baseRate Base rate for calculation
     * @param suggestedAdditiveFeePCT Suggested additive fee percentage
     * @return Calculated redemption rate
     */
    function calcRedemptionRate(address asset, uint baseRate, uint suggestedAdditiveFeePCT) external view override returns (uint) {
        uint8 version = getVersion(msg.sender);
        uint256 minRedemptionsFeePct = getMinRedemptionsFeePct(asset, version);
        uint256 maxRedemptionsFeePct = getMaxRedemptionsFeePct(asset, version);

        uint effectiveBaseRate =
            collateralToVersionToSettings[asset][version].feeSettings.baseRateType == BaseRateType.Local ?
                baseRate : _baseRate;

        return StableMath._min(
            StableMath._max(minRedemptionsFeePct, DYNAMIC_REDEMPTION_FEE_FLOOR) + effectiveBaseRate + suggestedAdditiveFeePCT,
            maxRedemptionsFeePct
        );
    }

    /**
     * @dev Gets all collaterals
     * @return Array of all collaterals
     */
    function getCollaterals() external view returns (Collateral[] memory) {
        return collaterals;
    }

    /**
     * @dev Gets all collaterals with their settings
     * @return Array of all collaterals with their settings
     */
    function getCollateralsWithSettings() external view returns (CollateralWithSettings[] memory) {
        CollateralWithSettings[] memory cws = new CollateralWithSettings[](collaterals.length);
        for (uint i = 0; i < collaterals.length; i++) {
            Collateral memory c = collaterals[i];
            Settings memory s = collateralToVersionToSettings[address(c.asset)][c.version];

            cws[i] = CollateralWithSettings({
                name: c.asset.name(),
                symbol: c.asset.symbol(),
                decimals: c.asset.decimals(),
                version: c.version,
                settings: s,
                activePool: c.activePool,
                collateralSurplusPool: c.collateralSurplusPool,
                defaultPool: c.defaultPool,
                asset: c.asset,
                priceFeed: c.priceFeed,
                sortedPositions: c.sortedPositions,
                positionManager: c.positionManager,
                sunset: c.sunset,
                availableRedemptionPoints: _redemptionPointsAt(address(c.asset), c.version, block.timestamp),
                availableLoanPoints: _loanPointsAt(address(c.asset), c.version, block.timestamp)
            });
        }

        return cws;
    }

    /**
     * @dev Gets all active collaterals
     * @return Array of active collaterals
     */
    function getActiveCollaterals() override public view returns (Collateral[] memory) {
        return activeCollaterals;
    }

    /**
     * @dev Gets all active collaterals with their settings
     * @return Array of active collaterals with their settings
     */
    function getActiveCollateralsWithSettings() external view returns (CollateralWithSettings[] memory) {
        CollateralWithSettings[] memory cws = new CollateralWithSettings[](activeCollaterals.length);
        for (uint i = 0; i < activeCollaterals.length; i++) {
            Collateral memory c = activeCollaterals[i];
            cws[i] = getActiveCollateralInstanceWithSettings(address(c.asset), c.version);
        }
        return cws;
    }

    /**
     * @dev Gets unique active collateral addresses
     * @return Array of unique active collateral addresses
     */
    function getUniqueActiveCollateralAddresses() override public view returns (address[] memory) {
        return uniqueActiveCollaterals.values();
    }

    /**
     * @dev Gets the debt cap for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Debt cap
     */
    function getDebtCap(address asset, uint8 version) external override view returns (uint) {
        return collateralToVersionToSettings[asset][version].debtCap;
    }

    /**
     * @dev Gets the Critical Collateralization Ratio (CCR) for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return CCR value
     */
    function getCCR(address asset, uint8 version) public override view returns (uint) {
        return collateralToVersionToSettings[asset][version].CCR;
    }

    /**
     * @dev Gets the Minimum Collateralization Ratio (MCR) for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return MCR value
     */
    function getMCR(address asset, uint8 version) public override view returns (uint) {
        return collateralToVersionToSettings[asset][version].MCR;
    }

    /**
     * @dev Gets the minimum borrowing fee percentage for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Minimum borrowing fee percentage
     */
    function getMinBorrowingFeePct(address asset, uint8 version) public override view returns (uint) {
        return collateralToVersionToSettings[asset][version].feeSettings.minBorrowingFeePct;
    }

    /**
     * @dev Gets the maximum borrowing fee percentage for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Maximum borrowing fee percentage
     */
    function getMaxBorrowingFeePct(address asset, uint8 version) public override view returns (uint) {
        return collateralToVersionToSettings[asset][version].feeSettings.maxBorrowingFeePct;
    }

    /**
     * @dev Gets the minimum redemptions fee percentage for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Minimum redemptions fee percentage
     */
    function getMinRedemptionsFeePct(address asset, uint8 version) public override view returns (uint) {
        return collateralToVersionToSettings[asset][version].feeSettings.minRedemptionsFeePct;
    }

    /**
     * @dev Gets the maximum redemptions fee percentage for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Maximum redemptions fee percentage
     */
    function getMaxRedemptionsFeePct(address asset, uint8 version) public view returns (uint) {
        return collateralToVersionToSettings[asset][version].feeSettings.maxRedemptionsFeePct;
    }

    /**
     * @dev Gets the base rate type for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return BaseRateType
     */
    function getBaseRateType(address asset, uint8 version) external view returns (BaseRateType) {
        _requireValidAndActiveVersion(asset, version);
        return collateralToVersionToSettings[asset][version].feeSettings.baseRateType;
    }

    /**
     * @dev Gets the base rate type for the caller's asset and version
     * @return BaseRateType
     */
    function getBaseRateType() external override view returns (BaseRateType) {
        address asset = getAsset(msg.sender);
        uint8 version = getVersion(msg.sender);
        _requireVersionExists(asset, version);

        return collateralToVersionToSettings[asset][version].feeSettings.baseRateType;
    }

    /**
     * @dev Requires that the commissioning period has passed for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function requireAfterCommissioningPeriod(address asset, uint8 version) public override view {
        return _requireAfterCommissioningPeriod(asset, version);
    }

    /**
     * @dev Requires that a specific asset and version is active
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function requireIsActive(address asset, uint8 version) public override view {
        require(
            _isActive(asset, version),
            "CollateralController: Collateral has not been activated"
        );
    }

    /**
     * @dev Gets the active collateral instance with settings for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return CollateralWithSettings struct containing collateral and its settings
     */
    function getActiveCollateralInstanceWithSettings(address asset, uint8 version) public view returns (CollateralWithSettings memory) {
        Collateral memory c = getCollateralInstance(asset, version);
        Settings memory s = collateralToVersionToSettings[address(c.asset)][c.version];

        return CollateralWithSettings({
            name: c.asset.name(),
            symbol: c.asset.symbol(),
            decimals: c.asset.decimals(),
            version: c.version,
            settings: s,
            activePool: c.activePool,
            collateralSurplusPool: c.collateralSurplusPool,
            defaultPool: c.defaultPool,
            asset: c.asset,
            priceFeed: c.priceFeed,
            sortedPositions: c.sortedPositions,
            positionManager: c.positionManager,
            sunset: c.sunset,
            availableRedemptionPoints: _redemptionPointsAt(address(c.asset), c.version, block.timestamp),
            availableLoanPoints: _loanPointsAt(address(c.asset), c.version, block.timestamp)
        });
    }

    /**
     * @dev Gets the collateral instance with settings for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return CollateralWithSettings struct containing collateral and its settings
     */
    function getCollateralInstanceWithSettings(address asset, uint8 version) public view returns (CollateralWithSettings memory) {
        _requireVersionExists(asset, version);

        Collateral memory c = _unsafeIndexCollateral(asset, version);
        Settings memory s = collateralToVersionToSettings[address(c.asset)][c.version];

        return CollateralWithSettings({
            name: c.asset.name(),
            symbol: c.asset.symbol(),
            decimals: c.asset.decimals(),
            version: c.version,
            settings: s,
            activePool: c.activePool,
            collateralSurplusPool: c.collateralSurplusPool,
            defaultPool: c.defaultPool,
            asset: c.asset,
            priceFeed: c.priceFeed,
            sortedPositions: c.sortedPositions,
            positionManager: c.positionManager,
            sunset: c.sunset,
            availableRedemptionPoints: _redemptionPointsAt(address(c.asset), c.version, block.timestamp),
            availableLoanPoints: _loanPointsAt(address(c.asset), c.version, block.timestamp)
        });
    }

    /**
     * @dev Gets the settings for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Settings struct containing collateral settings
     */
    function getSettings(address asset, uint8 version) external view returns (ICollateralController.Settings memory) {
        _requireVersionExists(asset, version);
        return collateralToVersionToSettings[asset][version];
    }

    /**
     * @dev Gets the collateral instance for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return Collateral struct
     */
    function getCollateralInstance(address asset, uint8 version) public view returns (Collateral memory) {
        _requireVersionExists(asset, version);
        require(_isActive(asset, version), "Collateral has been sunset or is still commissioning");
        return _unsafeIndexCollateral(asset, version);
    }

    /**
     * @dev Gets the version for a specific position manager
     * @param positionManager Address of the position manager
     * @return uint8 Version number
     */
    function getVersion(address positionManager) public view returns (uint8) {
        require(validPositionManager(positionManager), "This position manager has not been allocated a version");
        return positionManagerToVersion[positionManager];
    }

    /**
     * @dev Gets the asset address for a specific position manager
     * @param positionManager Address of the position manager
     * @return address Asset address
     */
    function getAsset(address positionManager) public view returns (address) {
        require(validPositionManager(positionManager), "This position manager has not been allocated a version");
        return positionManagerToAsset[positionManager];
    }

    /**
     * @dev Checks if the system is in recovery mode for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param price Current price of the asset
     * @return bool True if in recovery mode, false otherwise
     */
    function checkRecoveryMode(address asset, uint8 version, uint price) external returns (bool) {
        _requireVersionExists(asset, version);
        Collateral memory c = _unsafeIndexCollateral(asset, version);
        return c.positionManager.checkRecoveryMode(price);
    }

    /**
     * @dev Requires that there are no under-collateralized positions across all active collaterals
     */
    function requireNoUnderCollateralizedPositions() external {
        for (uint256 idx; idx < activeCollaterals.length; idx++) {
            Collateral memory collateral = activeCollaterals[idx];
            if (!excludedFromBackstopWithdrawalChecks[address(collateral.asset)][collateral.version]) {
                try collateral.priceFeed.fetchLowestPrice(
                    true, // If a collateral is in danger, then we should not release stables from the pool, since that may in fact be needed to carry out liquidations.
                    false // We are already fetching the lowest price, but since it is not for the purposes of debt creation, we can be less conservative.
                ) returns (uint price) {
                    address lowestPosition = collateral.sortedPositions.getLast();
                    uint ICR = collateral.positionManager.getCurrentICR(lowestPosition, price);
                    require(
                        ICR >= getMCR(address(collateral.asset), collateral.version),
                        "CollateralController: Cannot withdraw while there are positions with ICR < MCR"
                    );
                } catch {
                    // In the event of revert, do not lock users into the pool
                }
            }
        }
    }

    /**
     * @dev Checks if a position manager is valid
     * @param positionManager Address of the position manager
     * @return bool True if valid, false otherwise
     */
    function validPositionManager(address positionManager) public view returns (bool) {
        return positionManagerToVersion[positionManager] != 0;
    }

    /**
     * @dev Checks if a specific asset and version is decommissioned
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return bool True if decommissioned, false otherwise
     */
    function isDecommissioned(address asset, uint8 version) public override view returns (bool) {
        return _isDecommissioned(asset, version);
    }

    /**
     * @dev Checks if a specific asset and version has been decommissioned and its sunset period has elapsed
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return bool True if decommissioned and sunset period elapsed, false otherwise
     */
    function decommissionedAndSunsetPeriodElapsed(address asset, uint8 version) public view returns (bool) {
        _requireVersionExists(asset, version);
        return _decommissionedAndSunsetPeriodElapsed(_unsafeIndexCollateral(asset, version));
    }

    /**
     * @dev Checks if a position manager's collateral has been decommissioned and its sunset period has elapsed
     * @param pm Address of the position manager
     * @param collateral Address of the collateral asset
     * @return bool True if decommissioned and sunset period elapsed, false otherwise
     */
    function decommissionedAndSunsetPositionManager(address pm, address collateral) external override view returns (bool) {
        // In this case, we index collateral without a check to see if it's active, as this call will be made when
        // extracting collateral from a collateral instance which has been sunset.  Get version will handle validating that
        // the calling PM exists.
        return _decommissionedAndSunsetPeriodElapsed(_unsafeIndexCollateral(collateral, getVersion(pm)));
    }

    /**
     * @dev Gets the maximum loan points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return uint Maximum loan points
     */
    function getMaxLoanPoints(address asset, uint8 version) public view returns (uint) {
        return collateralToVersionToSettings[asset][version].loanSettings.maxLoanPoints;
    }
}
CollateralControllerState.sol 394 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../interfaces/ICollateralController.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../../common/Base.sol";
import "../BaseRateAbstract.sol";

abstract contract CollateralControllerState is BaseRateAbstract {
    using SafeMath for uint;
    // Constants which act as hard limits which even governance cannot bypass
    uint public constant COMMISSIONING_PERIOD = 3 days;

    // If Ethereum can't go below these, then nothing else can.
    uint constant internal MINIMUM_MCR = 1100000000000000000; // 110%
    uint constant internal MINIMUM_CCR = 1500000000000000000; // 150%

    // Mappings for collateral settings and management
    mapping(address => mapping(uint8 => ICollateralController.Settings)) public collateralToVersionToSettings;
    mapping(address => mapping(uint8 => uint)) internal collateralToVersionToArrayIndex;
    mapping(address => mapping(uint8 => uint)) internal activeCollateralToVersionToArrayIndex;
    mapping(address => mapping(uint8 => uint)) public collateralToVersionToTimeOfCommission;
    mapping(address => uint8) internal positionManagerToVersion;
    mapping(address => address) internal positionManagerToAsset;
    mapping(address => uint8) internal latestVersion; // starts at 1, latestVersion of 0 means not currently supported
    mapping(address => bool) internal wasPreviouslyRemoved;

    // Arrays to store collateral information
    ICollateralController.Collateral[] public collaterals;
    ICollateralController.Collateral[] public activeCollaterals;
    mapping(address => uint8) public activeVersions;
    EnumerableSet.AddressSet internal uniqueActiveCollaterals;
    mapping(address => bool) public registered;
    mapping(address => mapping(uint8 => bool)) public excludedFromBackstopWithdrawalChecks;

    //==================================================================//
    //--------------------------- Helpers ------------------------------//
    //==================================================================//

    /**
     * @dev Calculates the utilization percentage
     * @param availablePoints Available points
     * @param maxPoints Maximum points
     * @return uint256 Utilization percentage in 1e18 format
     */
    function _calculateUtilizationPercent(uint256 availablePoints, uint256 maxPoints) internal pure returns (uint256) {
        if (
            maxPoints == 0 ||               // Avoid division by zero
            availablePoints >= maxPoints    // 100% availability implies 0% utilization
        ) {
            return 0;
        }

        uint256 usedPoints = maxPoints - availablePoints;
        return (usedPoints * DECIMAL_PRECISION) / maxPoints; // in 1e18 format
    }

    /**
     * @dev Checks if a collateral is active
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return bool True if the collateral is active, false otherwise
     */
    function _isActive(address asset, uint8 version) internal view returns (bool) {
        if (activeCollaterals.length == 0) return false;

        uint idx = activeCollateralToVersionToArrayIndex[asset][version];

        if (idx <= activeCollaterals.length - 1) {
            ICollateralController.Collateral memory ac = activeCollaterals[idx];
            return ac.version == version && address(ac.asset) == asset;
        } else {
            return false;
        }
    }

    /**
     * @dev Checks if a specific asset and version is decommissioned
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @return bool True if decommissioned, false otherwise
     */
    function _isDecommissioned(address asset, uint8 version) internal view returns (bool) {
        _requireVersionExists(asset, version);
        return collateralToVersionToSettings[asset][version].decommissionedOn != 0;
    }

    /**
     * @dev Checks if a collateral is in the commissioning period
     * @param collateral Collateral struct
     * @return bool True if the collateral is in commissioning period, false otherwise
     */
    function _commissioning(ICollateralController.Collateral memory collateral) internal view returns (bool) {
        return block.timestamp < (collateralToVersionToTimeOfCommission[address(collateral.asset)][collateral.version] + COMMISSIONING_PERIOD);
    }

    /**
     * @dev Checks if a collateral was not previously active
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _wasNotPreviouslyActive(address asset, uint8 version) internal view {
        require(!_unsafeIndexCollateral(asset, version).sunset, "Collateral has already been sunset");
    }

    /**
     * @dev Checks if a collateral has been decommissioned and its sunset period has elapsed
     * @param c Collateral struct
     * @return bool True if decommissioned and sunset period elapsed, false otherwise
     */
    function _decommissionedAndSunsetPeriodElapsed(ICollateralController.Collateral memory c) internal view returns (bool) {
        ICollateralController.Settings memory s = collateralToVersionToSettings[address(c.asset)][c.version];
        return s.decommissionedOn != 0 && (block.timestamp - s.decommissionedOn) >= 12 weeks;
    }

    /**
     * @dev Requires that a version exists for a specific asset
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireVersionExists(address asset, uint8 version) internal view {
        require(version > 0, "CollateralController: Version of 0 denotes unsupported asset");
        require(version <= latestVersion[asset], "CollateralController: Version is higher than latest version for this asset");
    }

    /**
     * @dev Requires that a version is valid and active for a specific asset
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireValidAndActiveVersion(address asset, uint8 version) internal view {
        _requireVersionExists(asset, version);
        require(!_isDecommissioned(asset, version), "CollateralController: Collateral must be active");
    }

    /**
     * @dev Can be called to ensure that an asset was not completely decommissioned in the system previously.
     * @param asset Address of the collateral asset
     */
    function _requireNotPreviouslyCompletelyRemoved(address asset) internal view {
        // Completely decommissioned assets should be a rare case, as adding/removing collaterals is expensive from an
        // operational and comms point of view.  A completely removed asset may have had orphaned token extractions
        // in the singleton contracts within the system.  Complete collateral decommissions are the result of token migrations,
        // lack of adoption, or hacks/exploits.
        if (activeVersions[asset] == 0) {
            require(!wasPreviouslyRemoved[asset], "Previously completely decommissioned assets cannot be re-supported");
        }
    }

    /**
     * @dev Requires that the commissioning period has passed for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireAfterCommissioningPeriod(address asset, uint8 version) internal view {
        require(
            block.timestamp >= collateralToVersionToTimeOfCommission[asset][version] + COMMISSIONING_PERIOD,
            "CollateralController: Commissioning safety period has not passed"
        );
    }

    /**
     * @dev Requires that a version is active or sunsetting for a specific asset
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     */
    function _requireActiveOrSunsetting(address asset, uint8 version) internal view {
        _requireVersionExists(asset, version);
        require(!_decommissionedAndSunsetPeriodElapsed(_unsafeIndexCollateral(asset, version)), "Collateral has been sunset");
    }

    /**
    * @dev Retrieves a collateral struct without safety checks
    * @param asset Address of the collateral asset
    * @param version Version of the collateral
    * @return Collateral struct
    */
    function _unsafeIndexCollateral(address asset, uint8 version) internal view returns (ICollateralController.Collateral memory) {
        return collaterals[collateralToVersionToArrayIndex[asset][version]];
    }

    /**
     * @dev Sets the debt cap for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newDebtCap New debt cap value
     */
    function _setDebtCap(address asset, uint8 version, uint256 newDebtCap) internal {
        collateralToVersionToSettings[asset][version].debtCap = newDebtCap;
    }

    /**
     * @dev Sets a collateral as decommissioned
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * Sets the decommissioned timestamp and resets the debt cap to zero
     */
    function _setDecommissioned(address asset, uint8 version) internal {
        ICollateralController.Settings storage s = collateralToVersionToSettings[asset][version];
        s.decommissionedOn = block.timestamp;
        s.debtCap = 0;
    }

    /**
     * @dev Sets the maximum loan points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newMaxLoanPoints New maximum loan points
     * Ensures the loan regeneration rate doesn't exceed the new maximum
     * Adjusts available loan points if they exceed the new maximum
     */
    function _setMaxLoanPoints(address asset, uint8 version, uint newMaxLoanPoints) internal {
        ICollateralController.LoanSettings storage l = collateralToVersionToSettings[asset][version].loanSettings;
        require(l.loanRegenerationRate <= newMaxLoanPoints, "Loan Regeneration Rate must be <= max Loan Points");
        l.maxLoanPoints = newMaxLoanPoints;

        if (l.availableLoanPoints > newMaxLoanPoints) {
            l.availableLoanPoints = newMaxLoanPoints;
        }
    }

    /**
     * @dev Sets the available loan points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newAvailableLoanPoints New available loan points
     * Ensures the new available points don't exceed the maximum
     */
    function _setAvailableLoanPoints(address asset, uint8 version, uint newAvailableLoanPoints) internal {
        ICollateralController.LoanSettings storage l = collateralToVersionToSettings[asset][version].loanSettings;
        require(newAvailableLoanPoints <= l.maxLoanPoints, "Available loan points must be <= max Loan Points");
        l.availableLoanPoints = newAvailableLoanPoints;
    }

    /**
     * @dev Sets the loan regeneration rate for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newLoanRegenerationRate New loan regeneration rate
     * Ensures the new rate doesn't exceed the maximum loan points
     * Updates available loan points based on the current state before changing the rate
     */
    function _setLoanRegenerationRate(address asset, uint8 version, uint newLoanRegenerationRate) internal {
        require(
            newLoanRegenerationRate == 0 ||   // turns off redemptions
            newLoanRegenerationRate >= 60,    // minimum setting, denoting $1 capacity regen per second
            "Regen rate must be 60 and above, or zero"
        );

        ICollateralController.LoanSettings storage l = collateralToVersionToSettings[asset][version].loanSettings;
        require(newLoanRegenerationRate <= l.maxLoanPoints, "Loan Regeneration Rate must be <= max Loan Points");
        uint regeneratedWithOldSetting = _loanPointsAt(asset, version, block.timestamp);
        l.availableLoanPoints = regeneratedWithOldSetting;
        l.lastLoanRegenerationTimestamp = block.timestamp;
        l.loanRegenerationRate = newLoanRegenerationRate;
    }

    /**
     * @dev Sets the maximum redemption points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newMaxRedemptionPoints New maximum redemption points
     * Ensures the redemption regeneration rate doesn't exceed the new maximum
     * Adjusts available redemption points if they exceed the new maximum
     */
    function _setMaxRedemptionPoints(address asset, uint8 version, uint newMaxRedemptionPoints) internal {
        ICollateralController.RedemptionSettings storage r = collateralToVersionToSettings[asset][version].redemptionSettings;
        require(r.redemptionRegenerationRate <= newMaxRedemptionPoints, "Regeneration Rate must be <= max Redemption Points");
        r.maxRedemptionPoints = newMaxRedemptionPoints;

        if (r.availableRedemptionPoints > newMaxRedemptionPoints) {
            r.availableRedemptionPoints = newMaxRedemptionPoints;
        }
    }

    /**
     * @dev Sets the available redemption points for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newAvailableRedemptionPoints New available redemption points
     * Ensures the new available points don't exceed the maximum
     */
    function _setAvailableRedemptionPoints(address asset, uint8 version, uint newAvailableRedemptionPoints) internal {
        ICollateralController.RedemptionSettings storage r = collateralToVersionToSettings[asset][version].redemptionSettings;
        require(newAvailableRedemptionPoints <= r.maxRedemptionPoints, "Available redemption points must be <= max Redemption Points");
        r.availableRedemptionPoints = newAvailableRedemptionPoints;
    }

    /**
     * @dev Sets the redemption cooldown period for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param redemptionCooldownPeriod New redemption cooldown period
     */
    function _setRedemptionCooldownPeriod(address asset, uint8 version, uint redemptionCooldownPeriod) internal {
        ICollateralController.RedemptionSettings storage r = collateralToVersionToSettings[asset][version].redemptionSettings;
        r.redemptionCooldownPeriod = redemptionCooldownPeriod;
    }

    /**
     * @dev Sets the redemption regeneration rate for a specific asset and version
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param newRedemptionRegenerationRate New redemption regeneration rate
     * Ensures the new rate doesn't exceed the maximum redemption points
     * Updates available redemption points based on the current state before changing the rate
     */
    function _setRedemptionRegenerationRate(address asset, uint8 version, uint newRedemptionRegenerationRate) internal {
        require(
            newRedemptionRegenerationRate == 0 ||   // turns off redemptions
            newRedemptionRegenerationRate >= 60,    // minimum setting, denoting $1 capacity regen per second
            "Regen rate must be 60 and above, or zero"
        );

        ICollateralController.RedemptionSettings storage r = collateralToVersionToSettings[asset][version].redemptionSettings;
        require(newRedemptionRegenerationRate <= r.maxRedemptionPoints, "Regeneration Rate must be <= max Redemption Points");
        uint regeneratedWithOldSetting = _redemptionPointsAt(asset, version, block.timestamp);
        r.availableRedemptionPoints = regeneratedWithOldSetting;
        r.lastRedemptionRegenerationTimestamp = block.timestamp;
        r.redemptionRegenerationRate = newRedemptionRegenerationRate;
    }

    /**
     * @dev Calculates the loan points at a specific timestamp based on current settings
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param targetTimestamp Target timestamp for calculation
     * @return workingLoanPoints Calculated loan points
     */
    function _loanPointsAt(address asset, uint8 version, uint targetTimestamp)
    internal view returns (uint workingLoanPoints) {
        ICollateralController.LoanSettings memory l = collateralToVersionToSettings[asset][version].loanSettings;
        workingLoanPoints = _calculatePointsAt(
            l.lastLoanRegenerationTimestamp,
            l.loanRegenerationRate,
            l.availableLoanPoints,
            l.maxLoanPoints,
            targetTimestamp
        );
    }

    /**
     * @dev Calculates the redemption points at a specific timestamp based on current settings
     * @param asset Address of the collateral asset
     * @param version Version of the collateral
     * @param targetTimestamp Target timestamp for calculation
     * @return workingRedemptionPoints Calculated redemption points
     */
    function _redemptionPointsAt(address asset, uint8 version, uint targetTimestamp)
    internal view returns (uint workingRedemptionPoints) {
        ICollateralController.RedemptionSettings memory r = collateralToVersionToSettings[asset][version].redemptionSettings;
        workingRedemptionPoints = _calculatePointsAt(
            r.lastRedemptionRegenerationTimestamp,
            r.redemptionRegenerationRate,
            r.availableRedemptionPoints,
            r.maxRedemptionPoints,
            targetTimestamp
        );
    }

    /**
    * @dev Common function to calculate points based on regeneration parameters
    * @param lastTimestamp Last regeneration timestamp
    * @param regenerationRatePerMin Points regenerated per minute
    * @param availablePoints Current available points
    * @param maxPoints Maximum allowed points
    * @param targetTimestamp Target timestamp for calculation
    * @return workingPoints Calculated points
    */
    function _calculatePointsAt(
        uint lastTimestamp,
        uint regenerationRatePerMin,
        uint availablePoints,
        uint maxPoints,
        uint targetTimestamp
    ) internal pure returns (uint) {
        if (targetTimestamp <= lastTimestamp) {
            return availablePoints;
        }

        uint secondsElapsed = targetTimestamp - lastTimestamp;

        (bool safeMul, uint _regeneratedPoints) = secondsElapsed.tryMul(regenerationRatePerMin);
        uint regeneratedPoints = (safeMul ? _regeneratedPoints : type(uint).max) / 60;

        (bool safeAdd, uint newPoints) = availablePoints.tryAdd(regeneratedPoints);
        uint safeAddPoints = safeAdd ? newPoints : type(uint).max;

        bool isMaxRegeneration = safeAddPoints > maxPoints;
        return isMaxRegeneration ? maxPoints : safeAddPoints;
    }
}
NonPayableProxy.sol 78 lines
// 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 NonPayableProxy {
    /**
     * @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 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 {}
}
ActivePool.sol 168 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../../../interfaces/IActivePool.sol";
import "../../../interfaces/IPositionManager.sol";
import "../../../Guardable.sol";
import "../../../interfaces/IRecoverable.sol";

/**
 * @title ActivePool
 * @dev Contract for managing the active pool of collateral and stable debt in the system.
 *
 * Key features:
 *      1. Collateral Management: Tracks and manages the collateral balance in the system.
 *      2. Stable Debt Tracking: Keeps record of the total stable debt in the system.
 *      3. Access Control: Restricts certain functions to specific system contracts.
 *      4. Token Recovery: Allows extraction of orphaned tokens in case of a sunset event.
 */
contract ActivePool is Ownable, IActivePool, Guardable, IRecoverable {
    // Addresses of key contracts and tokens in the system
    address public collateralAssetAddress;
    address public positionControllerAddress;
    address public positionManagerAddress;
    address public backstopPoolAddress;
    address public defaultPoolAddress;

    // Internal state variables
    uint256 internal Collateral;  // Total collateral in the pool
    uint256 internal stableDebt;  // Total stable debt in the system

    /**
     * @dev Sets the addresses of key contracts in the system
     * @param _positionControllerAddress Address of the Position Controller contract
     * @param _positionManagerAddress Address of the Position Manager contract
     * @param _backstopPoolAddress Address of the Backstop Pool contract
     * @param _defaultPoolAddress Address of the Default Pool contract
     * @param _collateralAssetAddress Address of the Collateral Asset token
     */
    function setAddresses(
        address _positionControllerAddress,
        address _positionManagerAddress,
        address _backstopPoolAddress,
        address _defaultPoolAddress,
        address _collateralAssetAddress
    )
    external
    onlyOwner
    {
        collateralAssetAddress = _collateralAssetAddress;
        positionControllerAddress = _positionControllerAddress;
        positionManagerAddress = _positionManagerAddress;
        backstopPoolAddress = _backstopPoolAddress;
        defaultPoolAddress = _defaultPoolAddress;

        renounceOwnership();
    }

    /**
     * @dev Returns the current collateral balance in the pool
     * @return The amount of collateral in the pool
     */
    function getCollateral() external view override returns (uint) {
        return Collateral;
    }

    /**
     * @dev Returns the current stable debt in the system
     * @return The amount of stable debt
     */
    function getStableDebt() external view override returns (uint) {
        return stableDebt;
    }

    /**
     * @dev Sends collateral to a specified account
     * @param _account The address to receive the collateral
     * @param _amount The amount of collateral to send
     */
    function sendCollateral(address _account, uint _amount) external override {
        _requireCallerIsPCorPMorBP();
        Collateral -= _amount;
        emit ActivePoolCollateralBalanceUpdated(Collateral);
        emit CollateralSent(_account, _amount);
        require(IERC20(collateralAssetAddress).transfer(_account, _amount), "ActivePool: sending Collateral failed");
    }

    /**
     * @dev Receives collateral into the pool
     * @param asset The address of the collateral asset
     * @param amount The amount of collateral to receive
     */
    function receiveCollateral(address asset, uint amount) external override {
        _requireCallerIsPositionControllerOrDefaultPool();
        require(asset == collateralAssetAddress, "ActivePool: Was sent unexpected collateral");
        Collateral += amount;
        emit ActivePoolCollateralBalanceUpdated(Collateral);
    }

    /**
     * @dev Increases the stable debt in the system
     * @param _amount The amount to increase the stable debt by
     */
    function increaseStableDebt(uint _amount) external override {
        _requireCallerIsPCorPM();
        stableDebt += _amount;
        emit ActivePoolStableDebtUpdated(stableDebt);
    }

    /**
     * @dev Decreases the stable debt in the system
     * @param _amount The amount to decrease the stable debt by
     */
    function decreaseStableDebt(uint _amount) external override {
        _requireCallerIsPCorPMorBP();
        stableDebt -= _amount;
        emit ActivePoolStableDebtUpdated(stableDebt);
    }

    /**
     * @dev Checks if the caller is either the Position Controller or Default Pool
     */
    function _requireCallerIsPositionControllerOrDefaultPool() internal view {
        require(
            msg.sender == positionControllerAddress ||
            msg.sender == defaultPoolAddress,
            "ActivePool: Caller is neither PC nor Default Pool");
    }

    /**
     * @dev Checks if the caller is either the Position Controller, Position Manager, or Backstop Pool
     */
    function _requireCallerIsPCorPMorBP() internal view {
        require(
            msg.sender == positionControllerAddress ||
            msg.sender == positionManagerAddress ||
            msg.sender == backstopPoolAddress,
            "ActivePool: Caller is neither PC nor PM nor BP");
    }

    /**
     * @dev Checks if the caller is either the Position Controller or Position Manager
     */
    function _requireCallerIsPCorPM() internal view {
        require(
            msg.sender == positionControllerAddress ||
            msg.sender == positionManagerAddress,
            "ActivePool: Caller is neither PC nor PM");
    }

    /**
     * @dev Extracts orphaned tokens from the contract in case of a sunset event
     * @param asset The address of the asset to extract
     * @param version The version of the extraction (unused in this implementation)
     */
    function extractOrphanedTokens(address asset, uint8 version) external override onlyGuardian {
        require(
            IPositionManager(positionManagerAddress).isSunset(),
            "Orphaned tokens can only be extracted if collateral is sunset"
        );

        IERC20 collateral = IERC20(collateralAssetAddress);
        collateral.transfer(guardian(), collateral.balanceOf(address(this)));
    }
}
CollateralSurplusPool.sol 151 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../../../interfaces/ICollateralSurplusPool.sol";
import "../../../interfaces/IPositionManager.sol";
import "../../../Guardable.sol";
import "../../../interfaces/IRecoverable.sol";

/**
 * @title CollateralSurplusPool
 * @dev Contract for managing surplus collateral in the system.
 *
 * Key features:
 *      1. Surplus Tracking: Keeps track of surplus collateral for each account.
 *      2. Collateral Management: Manages the total collateral in the surplus pool.
 *      3. Claim Mechanism: Allows users to claim their surplus collateral.
 *      4. Access Control: Restricts certain functions to specific system contracts.
 *      5. Token Recovery: Allows extraction of orphaned tokens in case of a sunset event.
 */
contract CollateralSurplusPool is Ownable, ICollateralSurplusPool, Guardable, IRecoverable {
    string constant public NAME = "CollateralSurplusPool";

    // Addresses of key contracts and tokens in the system
    address public positionControllerAddress;
    address public positionManagerAddress;
    address public activePoolAddress;
    address public collateralAssetAddress;

    // Total collateral in the surplus pool
    uint256 internal Collateral;

    // Mapping of account addresses to their claimable surplus collateral
    mapping (address => uint) internal balances;

    /**
     * @dev Sets the addresses of key contracts in the system
     * @param _positionControllerAddress Address of the Position Controller contract
     * @param _positionManagerAddress Address of the Position Manager contract
     * @param _activePoolAddress Address of the Active Pool contract
     * @param _collateralAssetAddress Address of the Collateral Asset token
     */
    function setAddresses(
        address _positionControllerAddress,
        address _positionManagerAddress,
        address _activePoolAddress,
        address _collateralAssetAddress
    )
    external
    override
    onlyOwner
    {
        positionControllerAddress = _positionControllerAddress;
        positionManagerAddress = _positionManagerAddress;
        activePoolAddress = _activePoolAddress;
        collateralAssetAddress = _collateralAssetAddress;
        renounceOwnership();
    }

    /**
     * @dev Returns the total collateral in the surplus pool
     * @return The amount of collateral in the pool
     */
    function getCollateral() external view override returns (uint) {
        return Collateral;
    }

    /**
     * @dev Returns the surplus collateral balance for a specific account
     * @param _account The address of the account to query
     * @return The amount of surplus collateral for the account
     */
    function getUserCollateral(address _account) external view override returns (uint) {
        return balances[_account];
    }

    /**
     * @dev Accounts for surplus collateral for a specific account
     * @param _account The address of the account to update
     * @param _amount The amount of surplus collateral to add
     */
    function accountSurplus(address _account, uint _amount) external override {
        _requireCallerIsPositionManager();

        uint newAmount = balances[_account] + _amount;
        balances[_account] = newAmount;

        emit CollBalanceUpdated(_account, newAmount);
    }

    /**
     * @dev Allows an account to claim their surplus collateral
     * @param _account The address of the account claiming the collateral
     */
    function claimColl(address _account) external override {
        _requireCallerIsPositionController();
        uint claimableColl = balances[_account];
        require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

        balances[_account] = 0;
        emit CollBalanceUpdated(_account, 0);

        Collateral -= claimableColl;
        emit CollateralSent(_account, claimableColl);

        require(IERC20(collateralAssetAddress).transfer(_account, claimableColl), "CollSurplusPool: sending Collateral failed");
    }

    /**
     * @dev Receives collateral into the surplus pool
     * @param asset The address of the collateral asset
     * @param amount The amount of collateral to receive
     */
    function receiveCollateral(address asset, uint amount) external override {
        _requireCallerIsPositionManager();
        require(asset == collateralAssetAddress, "CollSurplusPool: Was sent unexpected collateral");
        Collateral += amount;
    }

    /**
     * @dev Checks if the caller is the Position Controller
     */
    function _requireCallerIsPositionController() internal view {
        require(msg.sender == positionControllerAddress, "CollSurplusPool: Caller is not Position Controller");
    }

    /**
     * @dev Checks if the caller is the Position Manager
     */
    function _requireCallerIsPositionManager() internal view {
        require(msg.sender == positionManagerAddress, "CollSurplusPool: Caller is not PositionManager");
    }

    /**
     * @dev Extracts orphaned tokens from the contract in case of a sunset event
     * @param asset The address of the asset to extract
     * @param version The version of the extraction (unused in this implementation)
     */
    function extractOrphanedTokens(address asset, uint8 version) external override onlyGuardian {
        require(
            IPositionManager(positionManagerAddress).isSunset(),
            "Orphaned tokens can only be extracted if collateral is sunset"
        );

        IERC20 collateral = IERC20(collateralAssetAddress);
        collateral.transfer(guardian(), collateral.balanceOf(address(this)));
    }
}
DefaultPool.sol 137 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../../../interfaces/IDefaultPool.sol";
import "../../../interfaces/IActivePool.sol";
import "../../../interfaces/IPositionManager.sol";
import "../../../Guardable.sol";
import "../../../interfaces/IRecoverable.sol";

/**
 * @title DefaultPool
 * @dev Contract for managing defaulted collateral and debt in the system.
 *
 * Key features:
 *      1. Collateral Management: Tracks and manages the defaulted collateral in the system.
 *      2. Stable Debt Tracking: Keeps record of the defaulted stable debt in the system.
 *      3. Collateral Transfer: Allows transfer of collateral to the Active Pool.
 *      4. Access Control: Restricts certain functions to the Position Manager.
 *      5. Token Recovery: Allows extraction of orphaned tokens in case of a sunset event.
 */
contract DefaultPool is Ownable, IDefaultPool, Guardable, IRecoverable {
    string constant public NAME = "DefaultPool";

    // Addresses of key contracts and tokens in the system
    address public collateralAssetAddress;
    address public positionManagerAddress;
    address public activePoolAddress;

    // Internal state variables
    uint256 internal Collateral;  // Total defaulted collateral in the pool
    uint256 internal stableDebt;  // Total defaulted stable debt in the system

    /**
     * @dev Sets the addresses of key contracts in the system
     * @param _positionManagerAddress Address of the Position Manager contract
     * @param _activePoolAddress Address of the Active Pool contract
     * @param _collateralAssetAddress Address of the Collateral Asset token
     */
    function setAddresses(
        address _positionManagerAddress,
        address _activePoolAddress,
        address _collateralAssetAddress
    ) external onlyOwner {
        collateralAssetAddress = _collateralAssetAddress;
        positionManagerAddress = _positionManagerAddress;
        activePoolAddress = _activePoolAddress;

        renounceOwnership();
    }

    /**
     * @dev Returns the current defaulted collateral balance in the pool
     * @return The amount of defaulted collateral in the pool
     */
    function getCollateral() external view override returns (uint) {
        return Collateral;
    }

    /**
     * @dev Returns the current defaulted stable debt in the system
     * @return The amount of defaulted stable debt
     */
    function getStableDebt() external view override returns (uint) {
        return stableDebt;
    }

    /**
     * @dev Sends collateral from the Default Pool to the Active Pool
     * @param _amount The amount of collateral to send
     */
    function sendCollateralToActivePool(uint _amount) external override {
        _requireCallerIsPositionManager();
        address activePool = activePoolAddress; // cache to save an SLOAD
        Collateral -= _amount;
        emit DefaultPoolCollateralBalanceUpdated(Collateral);
        emit CollateralSent(activePool, _amount);
        require(IERC20(collateralAssetAddress).transfer(activePool, _amount), "DefaultPool: sending collateral failed");
        IActivePool(activePool).receiveCollateral(collateralAssetAddress, _amount);
    }

    /**
     * @dev Receives collateral into the Default Pool
     * @param asset The address of the collateral asset
     * @param amount The amount of collateral to receive
     */
    function receiveCollateral(address asset, uint amount) external override {
        _requireCallerIsPositionManager();
        Collateral += amount;
        emit DefaultPoolCollateralBalanceUpdated(Collateral);
    }

    /**
     * @dev Increases the defaulted stable debt in the system
     * @param _amount The amount to increase the defaulted stable debt by
     */
    function increaseStableDebt(uint _amount) external override {
        _requireCallerIsPositionManager();
        stableDebt += _amount;
        emit DefaultPoolSTABLEDebtUpdated(stableDebt);
    }

    /**
     * @dev Decreases the defaulted stable debt in the system
     * @param _amount The amount to decrease the defaulted stable debt by
     */
    function decreaseStableDebt(uint _amount) external override {
        _requireCallerIsPositionManager();
        stableDebt -= _amount;
        emit DefaultPoolSTABLEDebtUpdated(stableDebt);
    }

    /**
     * @dev Checks if the caller is the Position Manager
     */
    function _requireCallerIsPositionManager() internal view {
        require(msg.sender == positionManagerAddress, "DefaultPool: Caller is not the PositionManager");
    }

    /**
     * @dev Extracts orphaned tokens from the contract in case of a sunset event
     * @param asset The address of the asset to extract
     * @param version The version of the extraction (unused in this implementation)
     */
    function extractOrphanedTokens(address asset, uint8 version) external override onlyGuardian {
        require(
            IPositionManager(positionManagerAddress).isSunset(),
            "Orphaned tokens can only be extracted if collateral is sunset"
        );

        IERC20 collateral = IERC20(collateralAssetAddress);
        collateral.transfer(guardian(), collateral.balanceOf(address(this)));
    }
}
LiquidationManager.sol 728 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../../../common/StableMath.sol";
import "../../../common/Base.sol";
import "../../../interfaces/IPositionManager.sol";
import "../../../interfaces/ISortedPositions.sol";
import "../../../interfaces/ICollateralSurplusPool.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../../interfaces/IPriceFeed.sol";
import "../../../interfaces/ICollateralController.sol";
import "./PositionState.sol";

/**
 * @title LiquidationManager
 * @dev Contract for managing the liquidation process of positions in the system.
 *
 * Key features:
 *      1. Liquidation Execution: Handles both individual and batch liquidations.
 *      2. Mode-specific Liquidation: Supports liquidations in both Normal and Recovery modes.
 *      3. Collateral Distribution: Manages the distribution of liquidated collateral.
 *      4. Debt Redistribution: Handles the redistribution of debt from liquidated positions.
 *      5. System State Updates: Updates system snapshots after liquidations.
 *      6. Gas Compensation: Provides gas compensation for liquidators.
 */
contract LiquidationManager is PositionState {
    // !!! IMPORTANT !!!: Do not reorder inheritance.
    // PositionState must start at storage slot zero, so that shared liquidation state with PositionManager is aligned.

    /**
     * @dev Liquidates multiple positions up to a specified number.
     * @param _n The maximum number of positions to liquidate.
     */
    function liquidatePositions(uint _n) external {
        ContractsCache memory contractsCache = ContractsCache(
            activePool,
            defaultPool,
            IStable(address(0)),
            IFeeTokenStaking(address(0)),
            sortedPositions,
            ICollateralSurplusPool(address(0))
        );
        IBackstopPool backstopPoolCached = backstopPool;
        LocalVariables_OuterLiquidationFunction memory vars;
        LiquidationTotals memory totals;

        uint8 version = collateralController.getVersion(address(this));

        /*
            A note on why we don't allow the circuit breaker to kick in during liquidations:
            For backstop depositors, buying collateral which is experiencing a rapid price drop, is part of the
            involved risks of being a backstop depositor.  We use a weighted average price to allow the short term
            TWAP to somewhat soften the price they are paying, but in the event where a collateral type is
            experiencing a massive drop, we need to clear that outstanding debt from the USDx supply.

            If we prevented clearing of debt, then by the time price stabilised, we would clear hardly any.
            The primary way to manage risk of highly volatile collateral types, and prevent too much overpriced debt
            being cleared, is by setting appropriate debt caps
        */
        vars.price = priceFeed.fetchWeightedAveragePrice(
            true, // Test liquidity to prevent manipulation of liquidation mechanism
            false // We are softening the deviation with an average, so no need to test it
        );
        vars.stablesInStabPool = backstopPoolCached.getTotalStableDeposits();
        vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
        uint MCR = collateralController.getMCR(address(collateralToken), collateralController.getVersion(address(this)));
        uint CCR = collateralController.getCCR(address(collateralToken), collateralController.getVersion(address(this)));

        // Perform the appropriate liquidation sequence - tally the values, and obtain their totals
        if (vars.recoveryModeAtStart) {
            totals = _getTotalsFromLiquidatePositionsSequence_RecoveryMode(contractsCache, vars.price, vars.stablesInStabPool, _n, MCR, CCR);
        } else { // if !vars.recoveryModeAtStart
            totals = _getTotalsFromLiquidatePositionsSequence_NormalMode(contractsCache.activePool, contractsCache.defaultPool, vars.price, vars.stablesInStabPool, _n, MCR);
        }

        require(totals.totalDebtInSequence > 0, "Nothing to liquidate");

        // Move liquidated Collateral and stables to the appropriate pools
        backstopPoolCached.offset(address(collateralToken), version, totals.totalDebtToOffset, totals.totalCollToSendToBP);
        _redistributeDebtAndColl(contractsCache.activePool, contractsCache.defaultPool, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
        if (totals.totalCollSurplus > 0) {
            contractsCache.activePool.sendCollateral(address(collateralSurplusPool), totals.totalCollSurplus);
            collateralSurplusPool.receiveCollateral(address(collateralToken), totals.totalCollSurplus);
        }

        // Update system snapshots
        _updateSystemSnapshots_excludeCollRemainder(contractsCache.activePool, totals.totalCollGasCompensation);

        vars.liquidatedDebt = totals.totalDebtInSequence;
        vars.liquidatedColl = (totals.totalCollInSequence - totals.totalCollGasCompensation) - totals.totalCollSurplus;
        emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalGasCompensation);

        // Send gas compensation to caller
        _sendGasCompensation(contractsCache.activePool, msg.sender, totals.totalGasCompensation, totals.totalCollGasCompensation);
    }

    /**
     * @dev Liquidates a batch of positions specified by their addresses.
     * @param _positionArray An array of position addresses to liquidate.
     */
    function batchLiquidatePositions(address[] memory _positionArray) external {
        require(_positionArray.length != 0, "Calldata address array must not be empty");

        IActivePool activePoolCached = activePool;
        IDefaultPool defaultPoolCached = defaultPool;
        IBackstopPool backstopPoolCached = backstopPool;

        LocalVariables_OuterLiquidationFunction memory vars;
        LiquidationTotals memory totals;

        uint8 version = collateralController.getVersion(address(this));

        /*
            A note on why we don't allow the circuit breaker to kick in during liquidations:
            For backstop depositors, buying collateral which is experiencing a rapid price drop, is part of the
            involved risks of being a backstop depositor.  We use a weighted average price to allow the short term
            TWAP to somewhat soften the price they are paying, but in the event where a collateral type is
            experiencing a massive drop, we need to clear that outstanding debt from the USDx supply.

            If we prevented clearing of debt, then by the time price stabilised, we would clear hardly any.
            The primary way to manage risk of highly volatile collateral types, and prevent too much overpriced debt
            being cleared, is by setting appropriate debt caps
        */
        vars.price = priceFeed.fetchWeightedAveragePrice(
            true, // Test liquidity to prevent manipulation of liquidation mechanism
            false // We are softening the deviation with an average, so no need to test it
        );
        vars.stablesInStabPool = backstopPoolCached.getTotalStableDeposits();
        vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
        uint MCR = collateralController.getMCR(address(collateralToken), collateralController.getVersion(address(this)));
        uint CCR = collateralController.getCCR(address(collateralToken), collateralController.getVersion(address(this)));

        // Perform the appropriate liquidation sequence - tally values and obtain their totals.
        if (vars.recoveryModeAtStart) {
            totals = _getTotalFromBatchLiquidate_RecoveryMode(activePoolCached, defaultPoolCached, vars.price, vars.stablesInStabPool, _positionArray, MCR, CCR);
        } else {  //  if !vars.recoveryModeAtStart
            totals = _getTotalsFromBatchLiquidate_NormalMode(activePoolCached, defaultPoolCached, vars.price, vars.stablesInStabPool, _positionArray, MCR);
        }

        require(totals.totalDebtInSequence > 0, "Nothing to liquidate");

        // Move liquidated Collateral and stable to the appropriate pools
        backstopPoolCached.offset(address(collateralToken), version, totals.totalDebtToOffset, totals.totalCollToSendToBP);
        _redistributeDebtAndColl(activePoolCached, defaultPoolCached, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
        if (totals.totalCollSurplus > 0) {
            activePoolCached.sendCollateral(address(collateralSurplusPool), totals.totalCollSurplus);
            collateralSurplusPool.receiveCollateral(address(collateralToken), totals.totalCollSurplus);
        }

        // Update system snapshots
        _updateSystemSnapshots_excludeCollRemainder(activePoolCached, totals.totalCollGasCompensation);

        vars.liquidatedDebt = totals.totalDebtInSequence;
        vars.liquidatedColl = (totals.totalCollInSequence - totals.totalCollGasCompensation) - totals.totalCollSurplus;
        emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalGasCompensation);

        // Send gas compensation to caller
        _sendGasCompensation(activePoolCached, msg.sender, totals.totalGasCompensation, totals.totalCollGasCompensation);
    }

    /**
     * @dev Calculates totals for liquidating positions in Normal Mode.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _price The current price of the collateral.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @param _n The maximum number of positions to liquidate.
     * @param MCR The Minimum Collateralization Ratio.
     * @return totals The calculated liquidation totals.
     */
    function _getTotalsFromLiquidatePositionsSequence_NormalMode(
        IActivePool _activePool,
        IDefaultPool _defaultPool,
        uint _price,
        uint _stablesInBackstopPool,
        uint _n,
        uint MCR
    )
    internal
    returns (LiquidationTotals memory totals)
    {
        LocalVariables_LiquidationSequence memory vars;
        LiquidationValues memory singleLiquidation;
        ISortedPositions sortedPositionsCached = sortedPositions;

        vars.remainingStablesInStabPool = _stablesInBackstopPool;

        for (vars.i = 0; vars.i < _n; vars.i++) {
            vars.user = sortedPositionsCached.getLast();
            vars.ICR = _getCurrentICR(vars.user, _price);

            if (vars.ICR < MCR) {
                singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingStablesInStabPool);

                vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;

                // Add liquidation values to their respective running totals
                totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

            } else break;  // break if the loop reaches a Position with ICR >= MCR
        }
    }

    /**
     * @dev Calculates totals for liquidating positions in Recovery Mode.
     * @param _contractsCache A cache of contract addresses.
     * @param _price The current price of the collateral.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @param _n The maximum number of positions to liquidate.
     * @param MCR The Minimum Collateralization Ratio.
     * @param CCR The Critical Collateralization Ratio.
     * @return totals The calculated liquidation totals.
     */
    function _getTotalsFromLiquidatePositionsSequence_RecoveryMode(
        ContractsCache memory _contractsCache,
        uint _price,
        uint _stablesInBackstopPool,
        uint _n,
        uint MCR,
        uint CCR
    )
    internal
    returns (LiquidationTotals memory totals)
    {
        LocalVariables_LiquidationSequence memory vars;
        LiquidationValues memory singleLiquidation;

        vars.remainingStablesInStabPool = _stablesInBackstopPool;
        vars.backToNormalMode = false;
        vars.entireSystemDebt = _getEntireDebt();
        vars.entireSystemColl = _getEntireCollateral();

        vars.user = _contractsCache.sortedPositions.getLast();
        address firstUser = _contractsCache.sortedPositions.getFirst();
        for (vars.i = 0; vars.i < _n && vars.user != firstUser; vars.i++) {
            // we need to cache it, because current user is likely going to be deleted
            address nextUser = _contractsCache.sortedPositions.getPrev(vars.user);

            vars.ICR = _getCurrentICR(vars.user, _price);

            if (!vars.backToNormalMode) {
                // Break the loop if ICR is greater than MCR and Backstop Pool is empty
                if (vars.ICR >= MCR && vars.remainingStablesInStabPool == 0) {break;}
                (vars, singleLiquidation, totals) = _performRecoveryLiquidation(vars, _contractsCache, singleLiquidation, totals, _price, CCR);
            }
            else if (vars.backToNormalMode && vars.ICR < MCR) {
                singleLiquidation = _liquidateNormalMode(_contractsCache.activePool, _contractsCache.defaultPool, vars.user, vars.remainingStablesInStabPool);

                vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;

                // Add liquidation values to their respective running totals
                totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

            } else break;  // break if the loop reaches a Position with ICR >= MCR

            vars.user = nextUser;
        }
    }

    /**
     * @dev Calculates totals for batch liquidating positions in Recovery Mode.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _price The current price of the collateral.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @param _positionArray An array of position addresses to liquidate.
     * @param MCR The Minimum Collateralization Ratio.
     * @param CCR The Critical Collateralization Ratio.
     * @return totals The calculated liquidation totals.
     */
    function _getTotalFromBatchLiquidate_RecoveryMode(
        IActivePool _activePool,
        IDefaultPool _defaultPool,
        uint _price,
        uint _stablesInBackstopPool,
        address[] memory _positionArray,
        uint MCR,
        uint CCR
    )
    internal
    returns (LiquidationTotals memory totals)
    {
        LocalVariables_LiquidationSequence memory vars;
        LiquidationValues memory singleLiquidation;

        vars.remainingStablesInStabPool = _stablesInBackstopPool;
        vars.backToNormalMode = false;
        vars.entireSystemDebt = _getEntireDebt();
        vars.entireSystemColl = _getEntireCollateral();

        for (vars.i = 0; vars.i < _positionArray.length; vars.i++) {
            vars.user = _positionArray[vars.i];
            // Skip non-active Positions
            if (Positions[vars.user].status != Status.active) {continue;}
            vars.ICR = _getCurrentICR(vars.user, _price);

            if (!vars.backToNormalMode) {

                // Skip this position if ICR is greater than MCR and Backstop Pool is empty
                if (vars.ICR >= MCR && vars.remainingStablesInStabPool == 0) {continue;}

                uint TCR = StableMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price, collateralToken.decimals());

                singleLiquidation = _liquidateRecoveryMode(_activePool, _defaultPool, vars.user, vars.ICR, vars.remainingStablesInStabPool, TCR, _price);

                // Update aggregate trackers
                vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;
                vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToOffset;
                vars.entireSystemColl =
                    ((vars.entireSystemColl - singleLiquidation.collToSendToBP) -
                        singleLiquidation.collGasCompensation) - singleLiquidation.collSurplus;

                // Add liquidation values to their respective running totals
                totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

                vars.backToNormalMode = !_checkPotentialRecoveryMode(vars.entireSystemColl, vars.entireSystemDebt, _price, CCR);
            }

            else if (vars.backToNormalMode && vars.ICR < MCR) {
                singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingStablesInStabPool);
                vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;

                // Add liquidation values to their respective running totals
                totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

            } else continue; // In Normal Mode skip Positions with ICR >= MCR
        }
    }

    /**
     * @dev Calculates totals for batch liquidating positions in Normal Mode.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _price The current price of the collateral.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @param _positionArray An array of position addresses to liquidate.
     * @param MCR The Minimum Collateralization Ratio.
     * @return totals The calculated liquidation totals.
     */
    function _getTotalsFromBatchLiquidate_NormalMode(
        IActivePool _activePool,
        IDefaultPool _defaultPool,
        uint _price,
        uint _stablesInBackstopPool,
        address[] memory _positionArray,
        uint MCR
    )
    internal
    returns (LiquidationTotals memory totals)
    {
        LocalVariables_LiquidationSequence memory vars;
        LiquidationValues memory singleLiquidation;

        vars.remainingStablesInStabPool = _stablesInBackstopPool;

        for (vars.i = 0; vars.i < _positionArray.length; vars.i++) {
            vars.user = _positionArray[vars.i];
            vars.ICR = _getCurrentICR(vars.user, _price);

            if (vars.ICR < MCR) {
                singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingStablesInStabPool);
                vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;

                // Add liquidation values to their respective running totals
                totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
            }
        }
    }

    // --- Liquidation helper functions ---

    /**
     * @dev Adds the values from a single liquidation to the running totals.
     * @param oldTotals The current running totals.
     * @param singleLiquidation The values from the single liquidation to add.
     * @return newTotals The updated running totals.
     */
    function _addLiquidationValuesToTotals(LiquidationTotals memory oldTotals, LiquidationValues memory singleLiquidation)
    internal pure returns (LiquidationTotals memory newTotals) {
        // Tally all the values with their respective running totals
        newTotals.totalCollGasCompensation = oldTotals.totalCollGasCompensation + singleLiquidation.collGasCompensation;
        newTotals.totalGasCompensation = oldTotals.totalGasCompensation + singleLiquidation.gasCompensation;
        newTotals.totalDebtInSequence = oldTotals.totalDebtInSequence + singleLiquidation.entirePositionDebt;
        newTotals.totalCollInSequence = oldTotals.totalCollInSequence + singleLiquidation.entirePositionColl;
        newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset + singleLiquidation.debtToOffset;
        newTotals.totalCollToSendToBP = oldTotals.totalCollToSendToBP + singleLiquidation.collToSendToBP;
        newTotals.totalDebtToRedistribute = oldTotals.totalDebtToRedistribute + singleLiquidation.debtToRedistribute;
        newTotals.totalCollToRedistribute = oldTotals.totalCollToRedistribute + singleLiquidation.collToRedistribute;
        newTotals.totalCollSurplus = oldTotals.totalCollSurplus + singleLiquidation.collSurplus;

        return newTotals;
    }

    /**
     * @dev Performs a liquidation in Recovery Mode.
     * @param vars Local variables for the liquidation sequence.
     * @param _contractsCache A cache of contract addresses.
     * @param singleLiquidation The liquidation values for a single position.
     * @param totals The running totals for the liquidation sequence.
     * @param _price The current price of the collateral.
     * @param CCR The Critical Collateralization Ratio.
     * @return Updated vars, singleLiquidation, and totals.
     */
    function _performRecoveryLiquidation(
        LocalVariables_LiquidationSequence memory vars,
        ContractsCache memory _contractsCache,
        LiquidationValues memory singleLiquidation,
        LiquidationTotals memory totals,
        uint _price,
        uint CCR
    ) private returns (
        LocalVariables_LiquidationSequence memory,
        LiquidationValues memory,
        LiquidationTotals memory
    ) {
        uint TCR = StableMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price, collateralToken.decimals());

        singleLiquidation = _liquidateRecoveryMode(_contractsCache.activePool, _contractsCache.defaultPool, vars.user, vars.ICR, vars.remainingStablesInStabPool, TCR, _price);

        // Update aggregate trackers
        vars.remainingStablesInStabPool = vars.remainingStablesInStabPool - singleLiquidation.debtToOffset;
        vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToOffset;
        vars.entireSystemColl =
            ((vars.entireSystemColl - singleLiquidation.collToSendToBP) -
                singleLiquidation.collGasCompensation) -
            singleLiquidation.collSurplus;

        // Add liquidation values to their respective running totals
        totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

        vars.backToNormalMode = !_checkPotentialRecoveryMode(vars.entireSystemColl, vars.entireSystemDebt, _price, CCR);

        return (vars, singleLiquidation, totals);
    }

    /**
     * @dev Liquidates a single position in Recovery Mode.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _borrower The address of the position's owner.
     * @param _ICR The Individual Collateralization Ratio of the position.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @param _TCR The Total Collateralization Ratio of the system.
     * @param _price The current price of the collateral.
     * @return singleLiquidation The liquidation values for the position.
     */
    function _liquidateRecoveryMode(
        IActivePool _activePool,
        IDefaultPool _defaultPool,
        address _borrower,
        uint _ICR,
        uint _stablesInBackstopPool,
        uint _TCR,
        uint _price
    )
    internal
    returns (LiquidationValues memory singleLiquidation)
    {
        LocalVariables_InnerSingleLiquidateFunction memory vars;
        if (PositionOwners.length <= 1) {return singleLiquidation;} // don't liquidate if last position
        (singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, vars.pendingDebtReward, vars.pendingCollReward) =
            _getEntireDebtAndColl(_borrower);

        singleLiquidation.collGasCompensation = _getCollGasCompensation(singleLiquidation.entirePositionColl);
        singleLiquidation.gasCompensation = GAS_COMPENSATION;
        vars.collToLiquidate = singleLiquidation.entirePositionColl - singleLiquidation.collGasCompensation;

        uint MCR = collateralController.getMCR(address(collateralToken), collateralController.getVersion(address(this)));

        // If ICR <= 100%, purely redistribute the Position across all active Position
        if (_ICR <= _100pct) {
            _movePendingPositionRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
            _removeStake(_borrower);

            singleLiquidation.debtToOffset = 0;
            singleLiquidation.collToSendToBP = 0;
            singleLiquidation.debtToRedistribute = singleLiquidation.entirePositionDebt;
            singleLiquidation.collToRedistribute = vars.collToLiquidate;

            _closePosition(_borrower, Status.closedByLiquidation);
            emit PositionLiquidated(_borrower, singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, uint8(PositionManagerOperation.liquidateInRecoveryMode));
            emit PositionUpdated(_borrower, 0, 0, 0, uint8(PositionManagerOperation.liquidateInRecoveryMode));

            // If 100% < ICR < MCR, offset as much as possible, and redistribute the remainder
        } else if ((_ICR > _100pct) && (_ICR < MCR)) {
            _movePendingPositionRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
            _removeStake(_borrower);

            (singleLiquidation.debtToOffset,
                singleLiquidation.collToSendToBP,
                singleLiquidation.debtToRedistribute,
                singleLiquidation.collToRedistribute) = _getOffsetAndRedistributionVals(singleLiquidation.entirePositionDebt, vars.collToLiquidate, _stablesInBackstopPool);

            _closePosition(_borrower, Status.closedByLiquidation);
            emit PositionLiquidated(_borrower, singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, uint8(PositionManagerOperation.liquidateInRecoveryMode));
            emit PositionUpdated(_borrower, 0, 0, 0, uint8(PositionManagerOperation.liquidateInRecoveryMode));
        } else if ((_ICR >= MCR) && (_ICR < _TCR) && (singleLiquidation.entirePositionDebt <= _stablesInBackstopPool)) {
            _movePendingPositionRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
            assert(_stablesInBackstopPool != 0);

            _removeStake(_borrower);
            singleLiquidation = _getCappedOffsetVals(singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, _price, MCR, GAS_COMPENSATION);

            _closePosition(_borrower, Status.closedByLiquidation);
            if (singleLiquidation.collSurplus > 0) {
                collateralSurplusPool.accountSurplus(_borrower, singleLiquidation.collSurplus);
            }

            emit PositionLiquidated(_borrower, singleLiquidation.entirePositionDebt, singleLiquidation.collToSendToBP, uint8(PositionManagerOperation.liquidateInRecoveryMode));
            emit PositionUpdated(_borrower, 0, 0, 0, uint8(PositionManagerOperation.liquidateInRecoveryMode));

        } else { // if (_ICR >= MCR && ( _ICR >= _TCR || singleLiquidation.entirePositionDebt > _stablesInBackstopPool))
            LiquidationValues memory zeroVals;
            return zeroVals;
        }

        return singleLiquidation;
    }

    // --- Inner single liquidation functions ---

    /**
     * @dev Liquidates a single position in Normal Mode.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _borrower The address of the position's owner.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @return singleLiquidation The liquidation values for the position.
     */
    function _liquidateNormalMode(IActivePool _activePool, IDefaultPool _defaultPool, address _borrower, uint _stablesInBackstopPool)
    internal
    returns (LiquidationValues memory singleLiquidation)
    {
        LocalVariables_InnerSingleLiquidateFunction memory vars;

        (singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, vars.pendingDebtReward, vars.pendingCollReward) =
        _getEntireDebtAndColl(_borrower);

        _movePendingPositionRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
        _removeStake(_borrower);

        singleLiquidation.collGasCompensation = _getCollGasCompensation(singleLiquidation.entirePositionColl);
        singleLiquidation.gasCompensation = GAS_COMPENSATION;
        uint collToLiquidate = singleLiquidation.entirePositionColl - singleLiquidation.collGasCompensation;

        (singleLiquidation.debtToOffset, singleLiquidation.collToSendToBP, singleLiquidation.debtToRedistribute, singleLiquidation.collToRedistribute) =
        _getOffsetAndRedistributionVals(singleLiquidation.entirePositionDebt, collToLiquidate, _stablesInBackstopPool);

        _closePosition(_borrower, Status.closedByLiquidation);
        emit PositionLiquidated(_borrower, singleLiquidation.entirePositionDebt, singleLiquidation.entirePositionColl, uint8(PositionManagerOperation.liquidateInNormalMode));
        emit PositionUpdated(_borrower, 0, 0, 0, uint8(PositionManagerOperation.liquidateInNormalMode));
        return singleLiquidation;
    }

    /**
     * @dev Calculates the values for offsetting and redistributing debt and collateral in a full liquidation.
     * @param _debt The total debt of the position.
     * @param _coll The total collateral of the position.
     * @param _stablesInBackstopPool The amount of stables in the backstop pool.
     * @return debtToOffset Amount of debt to be offset.
     * @return collToSendToBP Amount of collateral to be sent to the Backstop Pool.
     * @return debtToRedistribute Amount of debt to be redistributed.
     * @return collToRedistribute Amount of collateral to be redistributed.
     */
    function _getOffsetAndRedistributionVals
    (
        uint _debt,
        uint _coll,
        uint _stablesInBackstopPool
    )
    internal
    pure
    returns (uint debtToOffset, uint collToSendToBP, uint debtToRedistribute, uint collToRedistribute)
    {
        if (_stablesInBackstopPool > 0) {
            /*
            * Offset as much debt & collateral as possible against the Backstop Pool, and redistribute the remainder
            * between all active Positions.
            *
            *  If the position's debt is larger than the deposited stables in the Backstop Pool:
            *
            *  - Offset an amount of the position's debt equal to the stables in the Backstop Pool
            *  - Send a fraction of the position's collateral to the Backstop Pool, equal to the fraction of its offset debt
            *
            */
            debtToOffset = StableMath._min(_debt, _stablesInBackstopPool);
            collToSendToBP = (_coll * debtToOffset) / _debt;
            debtToRedistribute = _debt - debtToOffset;
            collToRedistribute = _coll - collToSendToBP;
        } else {
            debtToOffset = 0;
            collToSendToBP = 0;
            debtToRedistribute = _debt;
            collToRedistribute = _coll;
        }
    }

    /**
     * @dev Calculates the capped offset values for a position in Recovery Mode.
     * @param _entirePositionDebt The total debt of the position.
     * @param _entirePositionColl The total collateral of the position.
     * @param _price The current price of the collateral.
     * @param MCR The Minimum Collateralization Ratio.
     * @param gasComp The gas compensation amount.
     * @return singleLiquidation The liquidation values for the position.
     */
    function _getCappedOffsetVals
    (
        uint _entirePositionDebt,
        uint _entirePositionColl,
        uint _price,
        uint MCR,
        uint gasComp
    )
    internal
    pure
    returns (LiquidationValues memory singleLiquidation)
    {
        singleLiquidation.entirePositionDebt = _entirePositionDebt;
        singleLiquidation.entirePositionColl = _entirePositionColl;
        uint cappedCollPortion = (_entirePositionDebt * MCR) / _price;

        singleLiquidation.collGasCompensation = _getCollGasCompensation(cappedCollPortion);
        singleLiquidation.gasCompensation = gasComp;

        singleLiquidation.debtToOffset = _entirePositionDebt;
        singleLiquidation.collToSendToBP = cappedCollPortion - singleLiquidation.collGasCompensation;
        singleLiquidation.collSurplus = _entirePositionColl - cappedCollPortion;
        singleLiquidation.debtToRedistribute = 0;
        singleLiquidation.collToRedistribute = 0;
    }

    /**
     * @dev Updates system snapshots of total stakes and total collateral, excluding a given collateral remainder.
     * @param _activePool The active pool contract.
     * @param _collRemainder The amount of collateral to exclude from the snapshot.
     */
    function _updateSystemSnapshots_excludeCollRemainder(IActivePool _activePool, uint _collRemainder) internal {
        totalStakesSnapshot = totalStakes;
        uint activeColl = _activePool.getCollateral();
        uint liquidatedColl = defaultPool.getCollateral();
        totalCollateralSnapshot = (activeColl - _collRemainder) + liquidatedColl;
    }

    /**
     * @dev Redistributes debt and collateral from a liquidated position to all active positions.
     * @param _activePool The active pool contract.
     * @param _defaultPool The default pool contract.
     * @param _debt The amount of debt to redistribute.
     * @param _coll The amount of collateral to redistribute.
     */
    function _redistributeDebtAndColl(IActivePool _activePool, IDefaultPool _defaultPool, uint _debt, uint _coll) internal {
        if (_debt == 0) {return;}

        /*
        * Add distributed coll and debt rewards-per-unit-staked to the running totals. Division uses a "feedback"
        * error correction, to keep the cumulative error low in the running totals L_Collateral and L_StableDebt:
        *
        * 1) Form numerators which compensate for the floor division errors that occurred the last time this
        * function was called.
        * 2) Calculate "per-unit-staked" ratios.
        * 3) Multiply each ratio back by its denominator, to reveal the current floor division error.
        * 4) Store these errors for use in the next correction when this function is called.
        * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
        */
        uint CollateralNumerator = (_coll * DECIMAL_PRECISION) + lastCollateralError_Redistribution;
        uint stableDebtNumerator = (_debt * DECIMAL_PRECISION) + lastStableDebtError_Redistribution;

        // Get the per-unit-staked terms
        uint CollateralRewardPerUnitStaked = CollateralNumerator / totalStakes;
        uint stableDebtRewardPerUnitStaked = stableDebtNumerator / totalStakes;

        lastCollateralError_Redistribution = CollateralNumerator - (CollateralRewardPerUnitStaked * totalStakes);
        lastStableDebtError_Redistribution = stableDebtNumerator - (stableDebtRewardPerUnitStaked * totalStakes);

        // Add per-unit-staked terms to the running totals
        L_Collateral += CollateralRewardPerUnitStaked;
        L_StableDebt += stableDebtRewardPerUnitStaked;

        // Transfer coll and debt from ActivePool to DefaultPool
        _activePool.decreaseStableDebt(_debt);
        _defaultPool.increaseStableDebt(_debt);
        _activePool.sendCollateral(address(_defaultPool), _coll);
        _defaultPool.receiveCollateral(address(collateralToken), _coll);
    }

    /**
     * @dev Sends gas compensation to the liquidator.
     * @param _activePool The active pool contract.
     * @param _liquidator The address of the liquidator.
     * @param _stables The amount of stables for gas compensation.
     * @param _Collateral The amount of collateral for gas compensation.
     */
    function _sendGasCompensation(IActivePool _activePool, address _liquidator, uint _stables, uint _Collateral) internal {
        if (_stables > 0) {
            stableToken.returnFromPool(gasPoolAddress, _liquidator, _stables);
        }

        if (_Collateral > 0) {
            _activePool.sendCollateral(_liquidator, _Collateral);
        }
    }

    /**
     * @dev Checks if the system would be in Recovery Mode given the provided parameters.
     * @param _entireSystemColl The total collateral in the system.
     * @param _entireSystemDebt The total debt in the system.
     * @param _price The current price of the collateral.
     * @param CCR The Critical Collateralization Ratio.
     * @return A boolean indicating whether the system would be in Recovery Mode.
     */
    function _checkPotentialRecoveryMode(
        uint _entireSystemColl,
        uint _entireSystemDebt,
        uint _price,
        uint CCR
    )
    internal
    view
    returns (bool)
    {
        uint TCR = StableMath._computeCR(_entireSystemColl, _entireSystemDebt, _price, collateralToken.decimals());
        return TCR < CCR;
    }
}
PositionManager.sol 1297 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../../interfaces/IPositionManager.sol";
import "../../../interfaces/ISortedPositions.sol";
import "../../../interfaces/ICollateralSurplusPool.sol";
import "../../../interfaces/IPriceFeed.sol";
import "../../../common/StableMath.sol";
import "../../../interfaces/ICollateralController.sol";
import "./PositionState.sol";
import "./LiquidationManager.sol";
import "../../BaseRateAbstract.sol";
import "../../PermissionedRedeemerAbstract.sol";

/**
 * @title PositionManager
 * @dev This contract manages positions in the system, handling operations like redemptions, liquidations, and fee calculations.
 *
 * Key features:
 *  1. Position Management: Handles creation, modification, and closure of positions.
 *  2. Redemption Mechanism: Allows users to redeem collateral for stable tokens.
 *  3. Liquidation: Integrates with LiquidationManager for position liquidations.
 *  4. Fee Calculation: Computes borrowing and redemption fees based on system state.
 *  5. Reward Distribution: Manages the distribution of rewards to position holders.
 *  6. Optional redeemer whitelist to facilitate external services, or designated redeemers
 */
contract PositionManager is PositionState, IPositionManager, BaseRateAbstract, PermissionedRedeemerAbstract {
    IFeeTokenStaking public feeTokenStaking;
    using Address for address;
    address public liquidationManagerAddress;

    /**
     * @dev Struct to hold information about escrowed redemptions
     */
    struct EscrowedRedemption {
        uint stableAmount;
        uint timestamp;
        uint utilizationPCT;
        uint loadIncrease;
    }

    mapping(address => EscrowedRedemption) public escrowedRedemptions;

    /**
     * @dev Struct to hold totals for redemption operations
     */
    struct RedemptionTotals {
        uint remainingStables;
        uint totalStablesToRedeem;
        uint totalCollateralDrawn;
        uint CollateralFee;
        uint CollateralToSendToRedeemer;
        uint decayedBaseRate;
        uint price;
        uint totalStableSupplyAtStart;
        uint utilizationPCT;
        uint loadIncrease;
        uint maxFeePCT;
        uint suggestedAdditiveFeePCT;
        EscrowedRedemption redemption;
        uint _stableAmount;
        bool timedOut; // either didn't complete in time, or escrow requirement removed while in escrow
        uint cooldownRequirement;
        uint gracePeriod;
        uint8 version;
        uint redemptionsTimeoutFeePct;
        bool firstInLine;
    }

    /**
     * @dev Struct to hold values for a single redemption operation
     */
    struct SingleRedemptionValues {
        uint stableLot;
        uint CollateralLot;
        bool cancelledPartial;
    }

    /**
     * @dev Constructor to initialize the PositionManager contract
     * @param _collateralAsset Address of the collateral asset
     * @param _positionControllerAddress Address of the PositionController contract
     * @param _activePoolAddress Address of the ActivePool contract
     * @param _defaultPoolAddress Address of the DefaultPool contract
     * @param _backstopPoolAddress Address of the BackstopPool contract
     * @param _gasPoolAddress Address of the GasPool contract
     * @param _collSurplusPoolAddress Address of the CollateralSurplusPool contract
     * @param _priceFeedAddress Address of the PriceFeed contract
     * @param _stableTokenAddress Address of the StableToken contract
     * @param _sortedPositionsAddress Address of the SortedPositions contract
     * @param _feeTokenStakingAddress Address of the FeeTokenStaking contract
     * @param _collateralController Address of the CollateralController contract
     */
    constructor(
        address _collateralAsset,
        address _positionControllerAddress,
        address _activePoolAddress,
        address _defaultPoolAddress,
        address _backstopPoolAddress,
        address _gasPoolAddress,
        address _collSurplusPoolAddress,
        address _priceFeedAddress,
        address _stableTokenAddress,
        address _sortedPositionsAddress,
        address _feeTokenStakingAddress,
        address _collateralController
    ) {
        collateralController = ICollateralController(_collateralController);
        positionControllerAddress = _positionControllerAddress;
        collateralToken = IERC20Metadata(_collateralAsset);
        activePool = IActivePool(_activePoolAddress);
        defaultPool = IDefaultPool(_defaultPoolAddress);
        backstopPool = IBackstopPool(_backstopPoolAddress);
        gasPoolAddress = _gasPoolAddress;
        collateralSurplusPool = ICollateralSurplusPool(_collSurplusPoolAddress);
        priceFeed = IPriceFeed(_priceFeedAddress);
        stableToken = IStable(_stableTokenAddress);
        sortedPositions = ISortedPositions(_sortedPositionsAddress);
        feeTokenStaking = IFeeTokenStaking(_feeTokenStakingAddress);
        liquidationManagerAddress = address(new LiquidationManager());
    }

    /**
     * @dev Returns the count of position owners
     * @return The number of position owners
     */
    function getPositionOwnersCount() external view override returns (uint) {
        return PositionOwners.length;
    }

    /**
     * @dev Returns the Minimum Collateralization Ratio (MCR)
     * @return _MCR The Minimum Collateralization Ratio
     */
    function MCR() external view returns (uint _MCR) {
        _MCR = collateralController.getMCR(address(collateralToken), collateralController.getVersion(address(this)));
    }

    /**
     * @dev Returns the Critical Collateralization Ratio (CCR)
     * @return _CCR The Critical Collateralization Ratio
     */
    function CCR() external view returns (uint _CCR) {
        _CCR = collateralController.getCCR(address(collateralToken), collateralController.getVersion(address(this)));
    }

    /**
     * @dev Returns the address of a position owner at a given index
     * @param _index The index of the position owner
     * @return The address of the position owner
     */
    function getPositionFromPositionOwnersArray(uint _index) external view override returns (address) {
        return PositionOwners[_index];
    }

    /**
     * @dev Liquidates a single position
     * @param _borrower The address of the position to liquidate
     */
    function liquidate(address _borrower) external override {
        _requirePositionIsActive(_borrower);

        address[] memory borrowers = new address[](1);
        borrowers[0] = _borrower;
        batchLiquidatePositions(borrowers);
    }

    /**
     * @dev Liquidates multiple positions
     * @param _n The maximum number of positions to liquidate
     */
    function liquidatePositions(uint _n) external override {
        liquidationManagerAddress.functionDelegateCall(
            abi.encodeWithSignature("liquidatePositions(uint256)", _n),
            "liquidatePositions: delegate call failed"
        );
    }

    /**
     * @dev Batch liquidates a list of positions
     * @param _positionArray An array of addresses representing positions to liquidate
     */
    function batchLiquidatePositions(address[] memory _positionArray) public override {
        require(_positionArray.length != 0, "Calldata address array must not be empty");
        liquidationManagerAddress.functionDelegateCall(
            abi.encodeWithSignature("batchLiquidatePositions(address[])", _positionArray),
            "batchLiquidatePositions: delegate call failed"
        );
    }

    /**
     * @dev Redeems collateral from a position
     * @param collateral Cache of contract addresses
     * @param _borrower The address of the position to redeem from
     * @param _maxStableAmount The maximum amount of stable tokens to redeem
     * @param _price The current price of the collateral
     * @param _upperPartialRedemptionHint The upper bound for partial redemption
     * @param _lowerPartialRedemptionHint The lower bound for partial redemption
     * @param _partialRedemptionHintNICR The NICR hint for partial redemption
     * @return singleRedemption The redemption values
     */
    function _redeemCollateralFromPosition(
        ContractsCache memory collateral,
        address _borrower,
        uint _maxStableAmount,
        uint _price,
        address _upperPartialRedemptionHint,
        address _lowerPartialRedemptionHint,
        uint _partialRedemptionHintNICR,
        bool firstInLine
    )
    internal returns (SingleRedemptionValues memory singleRedemption)
    {
        singleRedemption.stableLot = StableMath._min(_maxStableAmount, Positions[_borrower].debt - GAS_COMPENSATION);
        singleRedemption.CollateralLot = (singleRedemption.stableLot * DECIMAL_PRECISION) / _price;

        uint newDebt = (Positions[_borrower].debt) - singleRedemption.stableLot;
        uint newColl = (Positions[_borrower].coll) - singleRedemption.CollateralLot;

        if (newDebt == GAS_COMPENSATION) {
            _closePositionWithRedemption(collateral, _borrower, GAS_COMPENSATION, newColl);
        } else {
            singleRedemption = _partialRedemption(
                singleRedemption,
                collateral,
                _borrower,
                _upperPartialRedemptionHint,
                _lowerPartialRedemptionHint,
                _partialRedemptionHintNICR,
                newDebt,
                newColl,
                GAS_COMPENSATION,
                firstInLine
            );
        }

        return singleRedemption;
    }

    /**
     * @dev Closes a position during redemption
     * @param collateral Cache of contract addresses
     * @param _borrower The address of the position to close
     * @param GAS_COMPENSATION The gas compensation amount
     * @param newColl The new collateral amount
     */
    function _closePositionWithRedemption(
        ContractsCache memory collateral,
        address _borrower,
        uint GAS_COMPENSATION,
        uint newColl
    ) internal {
        _removeStake(_borrower);
        _closePosition(_borrower, Status.closedByRedemption);
        _redeemClosePosition(collateral, _borrower, GAS_COMPENSATION, newColl);
        emit PositionUpdated(_borrower, 0, 0, 0, uint8(PositionManagerOperation.redeemCollateral));
    }

    /**
     * @dev Performs a partial redemption of a position
     * @param singleRedemption The current redemption values
     * @param collateral Cache of contract addresses
     * @param _borrower The address of the position
     * @param _upperPartialRedemptionHint The upper bound for partial redemption
     * @param _lowerPartialRedemptionHint The lower bound for partial redemption
     * @param _partialRedemptionHintNICR The NICR hint for partial redemption
     * @param newDebt The new debt amount after redemption
     * @param newColl The new collateral amount after redemption
     * @param GAS_COMPENSATION The gas compensation amount
     * @param firstInLine Whether the partial redemption was the first (IE only) redemption target
     * @return The updated redemption values
     */
    function _partialRedemption(
        SingleRedemptionValues memory singleRedemption,
        ContractsCache memory collateral,
        address _borrower,
        address _upperPartialRedemptionHint,
        address _lowerPartialRedemptionHint,
        uint _partialRedemptionHintNICR,
        uint newDebt,
        uint newColl,
        uint GAS_COMPENSATION,
        bool firstInLine
    ) internal returns (SingleRedemptionValues memory) {
        uint newNICR = StableMath._computeNominalCR(newColl, newDebt, collateralToken.decimals());

        if (
            ((newDebt - GAS_COMPENSATION) < MIN_NET_DEBT) ||
            ((newNICR != _partialRedemptionHintNICR) && !firstInLine)
        ) {
            singleRedemption.cancelledPartial = true;
            return singleRedemption;
        }

        collateral.sortedPositions.reInsert(_borrower, newNICR, _upperPartialRedemptionHint, _lowerPartialRedemptionHint);

        Positions[_borrower].debt = newDebt;
        Positions[_borrower].coll = newColl;
        _updateStakeAndTotalStakes(_borrower);

        emit PositionUpdated(_borrower, newDebt, newColl, Positions[_borrower].stake, uint8(PositionManagerOperation.redeemCollateral));

        return singleRedemption;
    }

    /**
     * @dev Closes a position and handles the redemption process
     * @param collateral Cache of contract addresses
     * @param _borrower The address of the position to close
     * @param _stables The amount of stables to burn
     * @param _Collateral The amount of collateral to handle
     */
    function _redeemClosePosition(ContractsCache memory collateral, address _borrower, uint _stables, uint _Collateral) internal {
        stableToken.burn(gasPoolAddress, _stables);
        collateral.activePool.decreaseStableDebt(_stables);

        collateral.collateralSurplusPool.accountSurplus(_borrower, _Collateral);
        collateral.activePool.sendCollateral(address(collateral.collateralSurplusPool), _Collateral);
        collateral.collateralSurplusPool.receiveCollateral(address(collateralToken), _Collateral);
    }

    /**
     * @dev Checks if the first redemption hint is valid
     * @param _sortedPositions The SortedPositions contract
     * @param _firstRedemptionHint The first redemption hint
     * @param _price The current price of the collateral
     * @param MCR The Minimum Collateralization Ratio
     * @return A boolean indicating if the hint is valid
     */
    function _isValidFirstRedemptionHint(ISortedPositions _sortedPositions, address _firstRedemptionHint, uint _price, uint MCR) internal view returns (bool) {
        if (_firstRedemptionHint == address(0) ||
            !_sortedPositions.contains(_firstRedemptionHint) ||
            getCurrentICR(_firstRedemptionHint, _price) < MCR
        ) {
            return false;
        }

        address nextPosition = _sortedPositions.getNext(_firstRedemptionHint);
        return nextPosition == address(0) || getCurrentICR(nextPosition, _price) < MCR;
    }

    /**
     * @dev Queues a redemption request
     * @param _stableAmount The amount of stable tokens to redeem
     */
    function queueRedemption(uint _stableAmount) external override {
        require(canRedeem(msg.sender), "Caller does not have redemption privileges");
        require(_stableAmount > 0, "Amount must be greater than zero");

        // lookup asset to ensure post activation/pre sunset state.
        uint8 associatedVersion = collateralController.getVersion(address(this));
        collateralController.getCollateralInstance(address(collateralToken), associatedVersion);

        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];
        require(redemption.timestamp == 0, "Redemption already queued");

        (uint cooldownRequirement,,) = collateralController.getRedemptionCooldownRequirement(address(collateralToken), associatedVersion);
        require(cooldownRequirement > 0, "Cooldown requirement must be greater than 0");

        stableToken.transferForRedemptionEscrow(msg.sender, address(this), _stableAmount);
        redemption.stableAmount = _stableAmount;
        redemption.timestamp = block.timestamp;
        (redemption.utilizationPCT, redemption.loadIncrease) =
            collateralController.regenerateAndConsumeRedemptionPoints(_stableAmount);
    }

    /**
    * @dev Allows a user to dequeue their escrowed stables
    * This provides a way to recover stables without fee, in the event that redemptions become impossible
    * through no fault of the enqueueing user due to cases like CR dropping to dangerous levels, or whitelist being enacted.
    */
    function emergencyDequeue() external {
        (uint price,) = priceFeed.fetchHighestPriceWithFeeSuggestion(0, 0, false, false);
        uint MCR = collateralController.getMCR(address(collateralToken), collateralController.getVersion(address(this)));

        require(
            (_getTCR(price) < MCR) || redemptionWhitelistOnAndUserWithoutRole(msg.sender),
            "TCR must be below MCR to emergency dequeue"
        );

        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];
        require(redemption.timestamp != 0, "No redemption queued");

        uint stablesToReturn = redemption.stableAmount;
        require(stableToken.transfer(msg.sender, stablesToReturn), "Stable transfer failed");

        delete escrowedRedemptions[msg.sender];
        emit EmergencyDequeueProcessed(msg.sender, stablesToReturn);
    }

    /**
     * @dev Checks if an address has an enqueued redemption
     * @param redeemer The address to check
     * @return A boolean indicating if the address has an enqueued redemption
     */
    function hasEnqueuedEscrow(address redeemer) public view returns (bool) {
        return escrowedRedemptions[redeemer].timestamp != 0;
    }

    /**
     * @dev Redeems collateral for stable tokens
     * @param _stableAmount The amount of stable tokens to redeem
     * @param _firstRedemptionHint The first redemption hint
     * @param _upperPartialRedemptionHint The upper bound for partial redemption
     * @param _lowerPartialRedemptionHint The lower bound for partial redemption
     * @param _partialRedemptionHintNICR The NICR hint for partial redemption
     * @param _maxIterations The maximum number of iterations for the redemption process
     * @param _maxFeePercentage The maximum fee percentage the redeemer is willing to pay
     */
    function redeemCollateral(
        uint _stableAmount,
        address _firstRedemptionHint,
        address _upperPartialRedemptionHint,
        address _lowerPartialRedemptionHint,
        uint _partialRedemptionHintNICR,
        uint _maxIterations,
        uint _maxFeePercentage
    ) external override {
        require(canRedeem(msg.sender), "Caller does not have redemption privileges");

        RedemptionTotals memory totals;

        ContractsCache memory contractsCache =
                        ContractsCache(activePool, defaultPool, stableToken, feeTokenStaking, sortedPositions, collateralSurplusPool);

        totals.version = collateralController.getVersion(address(this));
        totals.maxFeePCT = _maxFeePercentage;

        (totals.cooldownRequirement, totals.gracePeriod, totals.redemptionsTimeoutFeePct) =
            collateralController.getRedemptionCooldownRequirement(address(collateralToken), totals.version);

        (totals._stableAmount, totals.utilizationPCT, totals.loadIncrease, totals.timedOut) =
            _processRedemptionInputAccordingToSource(
                contractsCache,
                _stableAmount,
                totals.cooldownRequirement,
                totals.gracePeriod,
                totals.redemptionsTimeoutFeePct
            );

        if (totals.timedOut) {
            // user either failed to redeem from escrow in time,
            // or had existing escrow which needed to be dequeued after removal of escrow requirement
            return;
        }

        _requireAmountGreaterThanZero(totals._stableAmount);

        (totals.price, totals.suggestedAdditiveFeePCT) = priceFeed.fetchHighestPriceWithFeeSuggestion(
            totals.loadIncrease,
            totals.utilizationPCT,
            true,
            true
        );

        _requireValidMaxFeePercentage(totals.maxFeePCT, totals.suggestedAdditiveFeePCT);

        uint MCR = collateralController.getMCR(address(collateralToken), totals.version);
        _requireTCRoverMCR(totals.price, MCR);

        totals.totalStableSupplyAtStart = stableToken.totalSupply();
        assert(contractsCache.stableToken.balanceOf(msg.sender) <= totals.totalStableSupplyAtStart);

        totals.remainingStables = totals._stableAmount;
        address currentBorrower;

        if (_isValidFirstRedemptionHint(contractsCache.sortedPositions, _firstRedemptionHint, totals.price, MCR)) {
            currentBorrower = _firstRedemptionHint;
        } else {
            currentBorrower = contractsCache.sortedPositions.getLast();
            while (currentBorrower != address(0) && getCurrentICR(currentBorrower, totals.price) < MCR) {
                currentBorrower = contractsCache.sortedPositions.getPrev(currentBorrower);
            }
        }

        if (_maxIterations == 0) {_maxIterations = type(uint).max;}
        totals.firstInLine = true;

        while (currentBorrower != address(0) && totals.remainingStables > 0 && _maxIterations > 0) {
            _maxIterations--;
            address nextUserToCheck = contractsCache.sortedPositions.getPrev(currentBorrower);

            _applyPendingRewards(contractsCache.activePool, contractsCache.defaultPool, currentBorrower);

            SingleRedemptionValues memory singleRedemption = _redeemCollateralFromPosition(
                contractsCache,
                currentBorrower,
                totals.remainingStables,
                totals.price,
                _upperPartialRedemptionHint,
                _lowerPartialRedemptionHint,
                _partialRedemptionHintNICR,
                totals.firstInLine
            );

            totals.firstInLine = false;

            if (singleRedemption.cancelledPartial) break;

            totals.totalStablesToRedeem = totals.totalStablesToRedeem + singleRedemption.stableLot;
            totals.totalCollateralDrawn = totals.totalCollateralDrawn + singleRedemption.CollateralLot;

            totals.remainingStables = totals.remainingStables - singleRedemption.stableLot;
            currentBorrower = nextUserToCheck;
        }

        require(totals.totalCollateralDrawn > 0, "Unable to redeem any amount");

        if (totals.cooldownRequirement != 0) {
            _checkIfEscrowDepletedAndReEscrowUnusedStables(totals.remainingStables);
        }

        __updateBaseRateFromRedemption(totals.totalCollateralDrawn, totals.price, totals.totalStableSupplyAtStart);

        totals.CollateralFee = _getRedemptionFee(totals.totalCollateralDrawn, totals.suggestedAdditiveFeePCT);

        _requireUserAcceptsFee(totals.CollateralFee, totals.totalCollateralDrawn, totals.maxFeePCT);

        contractsCache.activePool.sendCollateral(address(contractsCache.feeTokenStaking), totals.CollateralFee);
        contractsCache.feeTokenStaking.increaseF_Collateral(address(collateralToken), totals.version, totals.CollateralFee);

        totals.CollateralToSendToRedeemer = totals.totalCollateralDrawn - totals.CollateralFee;

        emit Redemption(totals._stableAmount, totals.totalStablesToRedeem, totals.totalCollateralDrawn, totals.CollateralFee);

        contractsCache.stableToken.burn(msg.sender, totals.totalStablesToRedeem);
        contractsCache.activePool.decreaseStableDebt(totals.totalStablesToRedeem);
        contractsCache.activePool.sendCollateral(msg.sender, totals.CollateralToSendToRedeemer);
    }

    /**
     * @dev Reads back currently pending escrow details, checks timeout status, and debits amount to attempt.
     * @param attemptAmount The amount of stables the user is asking to redeem
     * @param escrowDuration Lockup time before redemptions can be attempted
     * @param claimGracePeriod Max amount of time a user has to clear their redemption amount
     * @return effectiveAmount The amount of stable tokens which are actually going to be processed
     * @return utilizationPCT The utilization percentage
     * @return loadIncrease The load increase
     * @return timedOut Whether the redemption timed out
     */
    function _validateAndReleaseStablesForRedemption(uint attemptAmount, uint escrowDuration, uint claimGracePeriod) private returns
    (uint effectiveAmount, uint utilizationPCT, uint loadIncrease, bool timedOut) {
        EscrowedRedemption storage redemption = escrowedRedemptions[msg.sender];

        require(redemption.timestamp != 0, "Redemption not queued");
        require(attemptAmount <= redemption.stableAmount, "Cannot redeem more than remaining queued in escrow");

        uint cooldownExpiry = redemption.timestamp + escrowDuration;
        uint gracePeriodExpiry = cooldownExpiry + claimGracePeriod;
        bool isGracePeriod = block.timestamp < gracePeriodExpiry;

        (utilizationPCT, loadIncrease) = (redemption.utilizationPCT, redemption.loadIncrease);

        if (isGracePeriod) {
            require(cooldownExpiry <= block.timestamp, "Redemption escrow timelock not satisfied");
            // debit from escrow, as we are about to try and use attemptAmount in a redemption
            // we will add the unused balance back once the redemption is complete
            timedOut = false;
            effectiveAmount = attemptAmount;
            redemption.stableAmount -= effectiveAmount;
        } else {
            // We are about to return stables to user and charge the timeout fee on total remaining amount
            timedOut = true;
            effectiveAmount = redemption.stableAmount;
            redemption.stableAmount = 0;
        }

        require(stableToken.transfer(msg.sender, effectiveAmount), "stable transfer failed");
    }

    /**
     * @dev Re-queues unused stables, or deletes escrow if all stables within have been utilised
     * @param unusedStables The amount of stables unused after the redemption attempt
     */
    function _checkIfEscrowDepletedAndReEscrowUnusedStables(uint unusedStables) private {
        EscrowedRedemption storage escrow = escrowedRedemptions[msg.sender];

        if (unusedStables == 0 && escrow.stableAmount == 0) {
            // if we used everything, the escrow is now exhausted and safe to delete
            delete escrowedRedemptions[msg.sender];
        } else if (unusedStables != 0) {
            // otherwise, transfer back left over stables for next attempt
            stableToken.transferForRedemptionEscrow(msg.sender, address(this), unusedStables);
            escrow.stableAmount += unusedStables;
        }
    }

    /**
    * @dev Performs validation and unpacking of redemption data
    * @param contractsCache Cache of contract references
    * @param requestedStables Amount of stables requested for redemption by the user
    * @param cooldownRequirement Required waiting period in escrow before redemptions can happen
    * @param gracePeriod Time given in which to clear the total redemption amount before suffering a fee
    * @param redemptionsTimeoutFeePct Fee percentage charged if redemption has timed out
    * @return effectiveStables Post validation amount of stables to redeem
    * @return utilizationPCT System utilization percentage at time of escrow
    * @return loadIncrease Impact on points utilization
    * @return timedOut Whether the redemption request has expired
    */
    function _processRedemptionInputAccordingToSource(
        ContractsCache memory contractsCache,
        uint requestedStables,
        uint cooldownRequirement,
        uint gracePeriod,
        uint redemptionsTimeoutFeePct
    ) private returns (uint effectiveStables, uint utilizationPCT, uint loadIncrease, bool timedOut) {
        if (cooldownRequirement != 0) {
            (effectiveStables, utilizationPCT, loadIncrease, timedOut) =
                _validateAndReleaseStablesForRedemption(requestedStables, cooldownRequirement, gracePeriod);

            // Effective stables should have been returned to redeemer during _validateAndReleaseStablesForRedemption
            _requireStableBalanceCoversRedemption(contractsCache.stableToken, msg.sender, effectiveStables);

            if (timedOut) {
                _chargeTimeoutFeeAndClearEscrow(contractsCache, effectiveStables, redemptionsTimeoutFeePct);
            }
        } else {
            if (hasEnqueuedEscrow(msg.sender)) {
                _dequeueEscrow();
                (effectiveStables, utilizationPCT, loadIncrease, timedOut) = (0, 0, 0, true);
            } else {
                timedOut = false;
                effectiveStables = requestedStables;

                // No escrow required, so we just need to check that user has at least requestedStables/effectiveStables
                _requireStableBalanceCoversRedemption(contractsCache.stableToken, msg.sender, effectiveStables);

                (utilizationPCT, loadIncrease) =
                    collateralController.regenerateAndConsumeRedemptionPoints(effectiveStables);
            }
        }
    }

    /**
    * @dev Charges a timeout fee and clears the escrow when a redemption request expires
    * @param contractsCache Cache of contract references
    * @param stablesNotClearedInTime Amount of stables that weren't redeemed before timeout
    * @param redemptionsTimeoutFeePct Percentage fee charged for timeout
    */
    function _chargeTimeoutFeeAndClearEscrow(
        ContractsCache memory contractsCache,
        uint stablesNotClearedInTime,
        uint redemptionsTimeoutFeePct
    ) private {
        uint timeoutFee = (redemptionsTimeoutFeePct * stablesNotClearedInTime) / DECIMAL_PRECISION;
        contractsCache.feeTokenStaking.increaseF_STABLE(timeoutFee);
        contractsCache.stableToken.transferForRedemptionEscrow(msg.sender, address(contractsCache.feeTokenStaking), timeoutFee);
        delete escrowedRedemptions[msg.sender];
    }

    /**
     * @dev Dequeues an escrowed redemption when cooldown requirement is zero
     */
    function _dequeueEscrow() private {
        EscrowedRedemption memory redemption = escrowedRedemptions[msg.sender];
        if (redemption.timestamp != 0) {
            require(stableToken.transfer(msg.sender, redemption.stableAmount), "stable transfer failed");
            delete escrowedRedemptions[msg.sender];
        }
    }

    // --- Helper functions ---

    /**
     * @dev Calculates the nominal ICR (Individual Collateralization Ratio) for a position
     * @param _borrower The address of the position owner
     * @return The nominal ICR
     */
    function getNominalICR(address _borrower) public view override returns (uint) {
        (uint currentCollateral, uint currentStableDebt) = _getCurrentPositionAmounts(_borrower);

        uint NICR = StableMath._computeNominalCR(currentCollateral, currentStableDebt, collateralToken.decimals());
        return NICR;
    }

    /**
     * @dev Calculates the current ICR (Individual Collateralization Ratio) for a position
     * @param _borrower The address of the position owner
     * @param _price The current price of the collateral
     * @return The current ICR
     */
    function getCurrentICR(address _borrower, uint _price) public view override returns (uint) {
        return _getCurrentICR(_borrower, _price);
    }

    /**
     * @dev Applies pending rewards to a position
     * @param _borrower The address of the position owner
     */
    function applyPendingRewards(address _borrower) external override {
        _requireCallerIsPositionController();
        return _applyPendingRewards(activePool, defaultPool, _borrower);
    }

    /**
     * @dev Internal function to apply pending rewards to a position
     * @param _activePool The active pool contract
     * @param _defaultPool The default pool contract
     * @param _borrower The address of the position owner
     */
    function _applyPendingRewards(IActivePool _activePool, IDefaultPool _defaultPool, address _borrower) internal {
        if (hasPendingRewards(_borrower)) {
            _requirePositionIsActive(_borrower);

            uint pendingCollateralReward = getPendingCollateralReward(_borrower);
            uint pendingStableDebtReward = getPendingStableDebtReward(_borrower);

            Positions[_borrower].coll = Positions[_borrower].coll + pendingCollateralReward;
            Positions[_borrower].debt = Positions[_borrower].debt + pendingStableDebtReward;

            _updatePositionRewardSnapshots(_borrower);

            _movePendingPositionRewardsToActivePool(_activePool, _defaultPool, pendingStableDebtReward, pendingCollateralReward);

            emit PositionUpdated(
                _borrower,
                Positions[_borrower].debt,
                Positions[_borrower].coll,
                Positions[_borrower].stake,
                uint8(PositionManagerOperation.applyPendingRewards)
            );
        }
    }

    /**
     * @dev Updates the reward snapshots for a position
     * @param _borrower The address of the position owner
     */
    function updatePositionRewardSnapshots(address _borrower) external override {
        _requireCallerIsPositionController();
        return _updatePositionRewardSnapshots(_borrower);
    }

    /**
     * @dev Internal function to update the reward snapshots for a position
     * @param _borrower The address of the position owner
     */
    function _updatePositionRewardSnapshots(address _borrower) internal {
        rewardSnapshots[_borrower].Collateral = L_Collateral;
        rewardSnapshots[_borrower].stableDebt = L_StableDebt;
    }

    /**
     * @dev Calculates the pending collateral reward for a position
     * @param _borrower The address of the position owner
     * @return The pending collateral reward
     */
    function getPendingCollateralReward(address _borrower) public view override returns (uint) {
        return _getPendingCollateralReward(_borrower);
    }

    /**
     * @dev Calculates the pending stable debt reward for a position
     * @param _borrower The address of the position owner
     * @return The pending stable debt reward
     */
    function getPendingStableDebtReward(address _borrower) public view override returns (uint) {
        return _getPendingStableDebtReward(_borrower);
    }

    /**
     * @dev Checks if a position has pending rewards
     * @param _borrower The address of the position owner
     * @return A boolean indicating if the position has pending rewards
     */
    function hasPendingRewards(address _borrower) public view override returns (bool) {
        if (Positions[_borrower].status != Status.active) {return false;}
        return (rewardSnapshots[_borrower].Collateral < L_Collateral);
    }

    /**
     * @dev Gets the entire debt and collateral for a position, including pending rewards
     * @param _borrower The address of the position owner
     * @return debt The total debt of the position
     * @return coll The total collateral of the position
     * @return pendingStableDebtReward The pending stable debt reward
     * @return pendingCollateralReward The pending collateral reward
     */
    function getEntireDebtAndColl(
        address _borrower
    )
    public
    view
    override
    returns (uint debt, uint coll, uint pendingStableDebtReward, uint pendingCollateralReward)
    {
        return _getEntireDebtAndColl(_borrower);
    }

    /**
     * @dev Removes the stake for a position
     * @param _borrower The address of the position owner
     */
    function removeStake(address _borrower) external override {
        _requireCallerIsPositionController();
        return _removeStake(_borrower);
    }

    /**
     * @dev Updates the stake and total stakes for a position
     * @param _borrower The address of the position owner
     * @return The new stake amount
     */
    function updateStakeAndTotalStakes(address _borrower) external override returns (uint) {
        _requireCallerIsPositionController();
        return _updateStakeAndTotalStakes(_borrower);
    }

    /**
     * @dev Internal function to update the stake and total stakes for a position
     * @param _borrower The address of the position owner
     * @return The new stake amount
     */
    function _updateStakeAndTotalStakes(address _borrower) internal returns (uint) {
        uint newStake = _computeNewStake(Positions[_borrower].coll);
        uint oldStake = Positions[_borrower].stake;
        Positions[_borrower].stake = newStake;
        totalStakes = (totalStakes - oldStake) + newStake;
        emit StakesUpdated(totalStakes);
        return newStake;
    }

    /**
     * @dev Computes a new stake based on the collateral amount
     * @param _coll The collateral amount
     * @return The computed stake
     */
    function _computeNewStake(uint _coll) internal view returns (uint) {
        uint stake;
        if (totalCollateralSnapshot == 0) {
            stake = _coll;
        } else {
            assert(totalStakesSnapshot > 0);
            stake = (_coll * totalStakesSnapshot) / totalCollateralSnapshot;
        }
        return stake;
    }

    /**
     * @dev Closes a position
     * @param _borrower The address of the position owner
     */
    function closePosition(address _borrower) external override {
        _requireCallerIsPositionController();
        return _closePosition(_borrower, Status.closedByOwner);
    }

    /**
     * @dev Adds a position owner to the array of position owners
     * @param _borrower The address of the position owner
     * @return index The index of the added position owner
     */
    function addPositionOwnerToArray(address _borrower) external override returns (uint index) {
        _requireCallerIsPositionController();
        return _addPositionOwnerToArray(_borrower);
    }

    /**
     * @dev Internal function to add a position owner to the array of position owners
     * @param _borrower The address of the position owner
     * @return index The index of the added position owner
     */
    function _addPositionOwnerToArray(address _borrower) internal returns (uint128 index) {
        PositionOwners.push(_borrower);
        index = uint128(PositionOwners.length - 1);
        Positions[_borrower].arrayIndex = index;
        return index;
    }

    // --- Redemption fee functions ---

    /**
     * @dev Calculates the redemption rate
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated redemption rate
     */
    function getRedemptionRate(uint suggestedAdditiveFeePCT) public view virtual returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            return _calcRedemptionRate(collateralController.getBaseRate(), suggestedAdditiveFeePCT);
        } else {
            return _calcRedemptionRate(_baseRate, suggestedAdditiveFeePCT);
        }
    }

    /**
     * @dev Calculates the redemption rate with decay
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated redemption rate with decay
     */
    function getRedemptionRateWithDecay(uint suggestedAdditiveFeePCT) public view override returns (uint) {
        return _calcRedemptionRate(__calcDecayedBaseRate(), suggestedAdditiveFeePCT);
    }

    /**
     * @dev Internal function to calculate the redemption rate
     * @param _baseRate The base rate
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated redemption rate
     */
    function _calcRedemptionRate(uint _baseRate, uint suggestedAdditiveFeePCT) internal view returns (uint) {
        return collateralController.calcRedemptionRate(address(collateralToken), _baseRate, suggestedAdditiveFeePCT);
    }

    /**
     * @dev Internal function to calculate the borrowing rate
     * @param _baseRate The base rate
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated borrowing rate
     */
    function _calcBorrowingRate(uint _baseRate, uint suggestedAdditiveFeePCT) internal view returns (uint) {
        return collateralController.calcBorrowingRate(address(collateralToken), _baseRate, suggestedAdditiveFeePCT);
    }

    /**
     * @dev Calculates the redemption fee
     * @param _CollateralDrawn The amount of collateral drawn
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated redemption fee
     */
    function _getRedemptionFee(uint _CollateralDrawn, uint suggestedAdditiveFeePCT) internal view returns (uint) {
        return _calcRedemptionFee(getRedemptionRate(suggestedAdditiveFeePCT), _CollateralDrawn);
    }

    /**
     * @dev Calculates the redemption fee with decay
     * @param _CollateralDrawn The amount of collateral drawn
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated redemption fee with decay
     */
    function getRedemptionFeeWithDecay(uint _CollateralDrawn, uint suggestedAdditiveFeePCT) external view override returns (uint) {
        return _calcRedemptionFee(getRedemptionRateWithDecay(suggestedAdditiveFeePCT), _CollateralDrawn);
    }

    /**
     * @dev Internal function to calculate the redemption fee
     * @param _redemptionRate The redemption rate
     * @param _CollateralDrawn The amount of collateral drawn
     * @return The calculated redemption fee
     */
    function _calcRedemptionFee(uint _redemptionRate, uint _CollateralDrawn) internal pure returns (uint) {
        uint redemptionFee = (_redemptionRate * _CollateralDrawn) / DECIMAL_PRECISION;
        require(redemptionFee < _CollateralDrawn, "Fee would eat up all returned collateral");
        return redemptionFee;
    }

    // --- Borrowing fee functions ---

    /**
     * @dev Calculates the borrowing rate with decay
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated borrowing rate with decay
     */
    function getBorrowingRateWithDecay(uint suggestedAdditiveFeePCT) public view override returns (uint) {
        return _calcBorrowingRate(__calcDecayedBaseRate(), suggestedAdditiveFeePCT);
    }

    /**
     * @dev Calculates the borrowing fee
     * @param _stableDebt The stable debt amount
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated borrowing fee
     */
    function getBorrowingFee(uint _stableDebt, uint suggestedAdditiveFeePCT) external view override returns (uint) {
        return _calcBorrowingFee(getBorrowingRate(suggestedAdditiveFeePCT), _stableDebt);
    }

    /**
     * @dev Calculates the borrowing fee with decay
     * @param _stableDebt The stable debt amount
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated borrowing fee with decay
     */
    function getBorrowingFeeWithDecay(uint _stableDebt, uint suggestedAdditiveFeePCT) external view override returns (uint) {
        return _calcBorrowingFee(getBorrowingRateWithDecay(suggestedAdditiveFeePCT), _stableDebt);
    }

    /**
     * @dev Internal function to calculate the borrowing fee
     * @param _borrowingRate The borrowing rate
     * @param _stableDebt The stable debt amount
     * @return The calculated borrowing fee
     */
    function _calcBorrowingFee(uint _borrowingRate, uint _stableDebt) internal pure returns (uint) {
        return (_borrowingRate * _stableDebt) / DECIMAL_PRECISION;
    }

    /**
     * @dev Decays the base rate from borrowing
     */
    function decayBaseRateFromBorrowing() external override {
        _requireCallerIsPositionController();

        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            collateralController.decayBaseRateFromBorrowing();
        } else {
            _decayBaseRateFromBorrowing();
        }
    }

    /**
     * @dev Ensures that the caller is the PositionController
     */
    function _requireCallerIsPositionController() internal view {
        require(msg.sender == positionControllerAddress, "Caller is not the PositionController");
    }

    /**
     * @dev Checks if a position is active
     * @param _borrower The address of the position owner
     */
    function _requirePositionIsActive(address _borrower) internal view {
        require(Positions[_borrower].status == Status.active, "Position does not exist or is closed");
    }

    /**
     * @dev Ensures that the redeemer has sufficient stable token balance for redemption
     * @param _stableToken The stable token contract
     * @param _redeemer The address of the redeemer
     * @param _amount The amount to be redeemed
     */
    function _requireStableBalanceCoversRedemption(IStable _stableToken, address _redeemer, uint _amount) internal view {
        require(_stableToken.balanceOf(_redeemer) >= _amount, "Requested redemption amount must be <= user's stable token balance");
    }

    /**
     * @dev Checks if the given amount is greater than zero
     * @param _amount The amount to check
     */
    function _requireAmountGreaterThanZero(uint _amount) internal pure {
        require(_amount > 0, "Amount must be greater than zero");
    }

    /**
     * @dev Retrieves the total debt in the system
     * @return total The total debt
     */
    function getEntireDebt() public override view returns (uint total) {
        return _getEntireDebt();
    }

    /**
     * @dev Retrieves the total collateral in the system
     * @return total The total collateral
     */
    function getEntireCollateral() public override view returns (uint total) {
        return _getEntireCollateral();
    }

    /**
     * @dev Calculates the Total Collateralization Ratio (TCR)
     * @param _price The current price of the collateral
     * @return TCR The Total Collateralization Ratio
     */
    function getTCR(uint _price) external view override returns (uint TCR) {
        return _getTCR(_price);
    }

    /**
     * @dev Checks if the system is in Recovery Mode
     * @param _price The current price of the collateral
     * @return A boolean indicating if the system is in Recovery Mode
     */
    function checkRecoveryMode(uint _price) external view override returns (bool) {
        return _checkRecoveryMode(_price);
    }

    /**
     * @dev Checks if the PositionManager has been sunset
     * @return A boolean indicating if the PositionManager is sunset
     */
    function isSunset() external view override returns (bool) {
        return collateralController.decommissionedAndSunsetPositionManager(address(this), address(collateralToken));
    }

    /**
     * @dev Ensures that the Total Collateralization Ratio (TCR) is above the Minimum Collateralization Ratio (MCR)
     * @param _price The current price of the collateral
     * @param MCR The Minimum Collateralization Ratio
     */
    function _requireTCRoverMCR(uint _price, uint MCR) internal view {
        require(_getTCR(_price) >= MCR, "Cannot redeem when TCR < MCR");
    }

    /**
     * @dev Validates the maximum fee percentage
     * @param _maxFeePercentage The maximum fee percentage specified
     * @param volatilityFee The current volatility fee
     */
    function _requireValidMaxFeePercentage(uint _maxFeePercentage, uint volatilityFee) internal view {
        uint8 version = collateralController.getVersion(address(this));
        uint256 minRedemptionsFeePct = collateralController.getMinRedemptionsFeePct(address(collateralToken), version);
        require(
            _maxFeePercentage >= StableMath._max(DYNAMIC_REDEMPTION_FEE_FLOOR, minRedemptionsFeePct) + volatilityFee &&
            _maxFeePercentage <= DECIMAL_PRECISION,
            "Max fee percentage must be between 0.5% and 100%"
        );
    }

    /**
     * @dev Calculates the borrowing rate
     * @param suggestedAdditiveFeePCT The suggested additive fee percentage
     * @return The calculated borrowing rate
     */
    function getBorrowingRate(uint suggestedAdditiveFeePCT) public view override returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            return _calcBorrowingRate(collateralController.getBaseRate(), suggestedAdditiveFeePCT);
        } else {
            return _calcBorrowingRate(_baseRate, suggestedAdditiveFeePCT);
        }
    }

    /**
     * @dev Updates the base rate from redemption
     * @param _CollateralDrawn The amount of collateral drawn
     * @param _price The current price of the collateral
     * @param _totalStableSupply The total supply of stable tokens
     * @return The updated base rate
     */
    function __updateBaseRateFromRedemption(uint _CollateralDrawn, uint _price, uint _totalStableSupply) internal virtual returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            return collateralController.updateBaseRateFromRedemption(_CollateralDrawn, _price, _totalStableSupply);
        } else {
            return _updateBaseRateFromRedemption(_CollateralDrawn, _price, _totalStableSupply);
        }
    }

    /**
     * @dev Retrieves the current base rate
     * @return The current base rate
     */
    function baseRate() external virtual view returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            return collateralController.getBaseRate();
        } else {
            return _baseRate;
        }
    }

    /**
     * @dev Retrieves the timestamp of the last fee operation
     * @return The timestamp of the last fee operation
     */
    function lastFeeOperationTime() external virtual view returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if (brt == ICollateralController.BaseRateType.Global) {
            return collateralController.getLastFeeOperationTime();
        } else {
            return _lastFeeOperationTime;
        }
    }

    /**
     * @dev Updates the timestamp of the last fee operation
     */
    function __updateLastFeeOpTime() internal virtual {
        ICollateralControl...

// [truncated — 54969 bytes total]
PositionState.sol 365 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../../interfaces/IActivePool.sol";
import "../../../interfaces/IDefaultPool.sol";
import "../../../interfaces/ISortedPositions.sol";
import "../../../common/StableMath.sol";
import "../../../interfaces/ICollateralSurplusPool.sol";
import "../../../interfaces/ICollateralController.sol";
import "../../../common/Base.sol";
import "../../../interfaces/IFeeTokenStaking.sol";
import "../../../interfaces/IStable.sol";

/**
 * @title PositionState
 * @dev Abstract contract that manages the state of positions in the system.
 * It includes data structures and functions for handling liquidations, rewards, and position management.
 */
abstract contract PositionState is Base {
    // Events
    event Liquidation(uint _liquidatedDebt, uint _liquidatedColl, uint _collGasCompensation, uint _gasCompensation);
    event PositionUpdated(address indexed _borrower, uint _debt, uint _coll, uint stake, uint8 operation);
    event PositionLiquidated(address indexed _borrower, uint _debt, uint _coll, uint8 operation);
    event PositionIndexMoved(address _borrower, uint _newIndex);
    event StakesUpdated(uint _newTotalStakes);
    event EmergencyDequeueProcessed(address indexed redeemer, uint stableAmount);


    /**
     * @dev Struct to cache contract addresses to save gas
     */
    struct ContractsCache {
        IActivePool activePool;
        IDefaultPool defaultPool;
        IStable stableToken;
        IFeeTokenStaking feeTokenStaking;
        ISortedPositions sortedPositions;
        ICollateralSurplusPool collateralSurplusPool;
    }

    // Variable container structs for liquidations

    /**
     * @dev Struct for variables used in the outer liquidation function
     */
    struct LocalVariables_OuterLiquidationFunction {
        uint price;
        uint stablesInStabPool;
        bool recoveryModeAtStart;
        uint liquidatedDebt;
        uint liquidatedColl;
    }

    /**
     * @dev Struct for tracking liquidation totals
     */
    struct LiquidationTotals {
        uint totalCollInSequence;
        uint totalDebtInSequence;
        uint totalCollGasCompensation;
        uint totalGasCompensation;
        uint totalDebtToOffset;
        uint totalCollToSendToBP;
        uint totalDebtToRedistribute;
        uint totalCollToRedistribute;
        uint totalCollSurplus;
    }

    /**
     * @dev Struct for variables used in the liquidation sequence
     */
    struct LocalVariables_LiquidationSequence {
        uint remainingStablesInStabPool;
        uint i;
        uint ICR;
        address user;
        bool backToNormalMode;
        uint entireSystemDebt;
        uint entireSystemColl;
    }

    /**
     * @dev Struct for storing liquidation values
     */
    struct LiquidationValues {
        uint entirePositionDebt;
        uint entirePositionColl;
        uint collGasCompensation;
        uint gasCompensation;
        uint debtToOffset;
        uint collToSendToBP;
        uint debtToRedistribute;
        uint collToRedistribute;
        uint collSurplus;
    }

    /**
     * @dev Struct for variables used in the inner single liquidate function
     */
    struct LocalVariables_InnerSingleLiquidateFunction {
        uint collToLiquidate;
        uint pendingDebtReward;
        uint pendingCollReward;
    }

    /**
     * @dev Enum representing the status of a position
     */
    enum Status {
        nonExistent,
        active,
        closedByOwner,
        closedByLiquidation,
        closedByRedemption
    }

    /**
     * @dev Struct representing a position
     */
    struct Position {
        uint debt;
        uint coll;
        uint stake;
        Status status;
        uint128 arrayIndex;
    }

    /**
     * @dev Enum representing different operations in the PositionManager
     */
    enum PositionManagerOperation {
        applyPendingRewards,
        liquidateInNormalMode,
        liquidateInRecoveryMode,
        redeemCollateral
    }

    // Contract interfaces
    IStable public stableToken;
    IPriceFeed public priceFeed;
    IActivePool public activePool;
    IDefaultPool public defaultPool;
    IBackstopPool public backstopPool;
    IERC20Metadata public collateralToken;
    ISortedPositions public sortedPositions;
    ICollateralController public collateralController;
    ICollateralSurplusPool public collateralSurplusPool;

    // Mappings and state variables
    mapping (address => RewardSnapshot) public rewardSnapshots;

    /**
     * @dev Struct for storing reward snapshots for a position
     */
    struct RewardSnapshot { uint Collateral; uint stableDebt;}

    address[] public PositionOwners;
    mapping (address => Position) public Positions;
    uint public totalStakes;
    uint public totalStakesSnapshot;

    // Snapshot of the total collateral across the ActivePool and DefaultPool, immediately after the latest liquidation.
    uint public totalCollateralSnapshot;
    uint public L_Collateral;
    uint public L_StableDebt;
    uint public lastCollateralError_Redistribution;
    uint public lastStableDebtError_Redistribution;

    /**
     * @dev Moves pending position rewards from the Default Pool to the Active Pool
     * @param _activePool The Active Pool contract
     * @param _defaultPool The Default Pool contract
     * @param _stables The amount of stables to move
     * @param _Collateral The amount of collateral to move
     */
    function _movePendingPositionRewardsToActivePool(IActivePool _activePool, IDefaultPool _defaultPool, uint _stables, uint _Collateral) internal {
        _defaultPool.decreaseStableDebt(_stables);
        _activePool.increaseStableDebt(_stables);
        _defaultPool.sendCollateralToActivePool(_Collateral);
    }

    /**
     * @dev Removes a borrower's stake from the total stakes
     * @param _borrower The address of the borrower
     */
    function _removeStake(address _borrower) internal {
        uint stake = Positions[_borrower].stake;
        totalStakes = totalStakes - stake;
        emit StakesUpdated(totalStakes);
        Positions[_borrower].stake = 0;
    }

    /**
     * @dev Closes a position
     * @param _borrower The address of the borrower
     * @param closedStatus The new status of the closed position
     */
    function _closePosition(address _borrower, Status closedStatus) internal {
        assert(closedStatus != Status.nonExistent && closedStatus != Status.active);

        uint PositionOwnersArrayLength = PositionOwners.length;
        _requireMoreThanOnePositionInSystem(PositionOwnersArrayLength);

        Positions[_borrower].status = closedStatus;
        Positions[_borrower].coll = 0;
        Positions[_borrower].debt = 0;

        rewardSnapshots[_borrower].Collateral = 0;
        rewardSnapshots[_borrower].stableDebt = 0;

        _removePositionOwner(_borrower, PositionOwnersArrayLength);
        sortedPositions.remove(_borrower);
    }

    /**
     * @dev Requires that there is more than one position in the system
     * @param PositionOwnersArrayLength The length of the PositionOwners array
     */
    function _requireMoreThanOnePositionInSystem(uint PositionOwnersArrayLength) internal view {
        require (PositionOwnersArrayLength > 1 && sortedPositions.getSize() > 1, "Only one position in the system");
    }

    /**
     * @dev Removes a position owner from the PositionOwners array
     * @param _borrower The address of the borrower to remove
     * @param PositionOwnersArrayLength The length of the PositionOwners array
     */
    function _removePositionOwner(address _borrower, uint PositionOwnersArrayLength) internal {
        Status PositionStatus = Positions[_borrower].status;
        assert(PositionStatus != Status.nonExistent && PositionStatus != Status.active);

        uint128 index = Positions[_borrower].arrayIndex;
        uint length = PositionOwnersArrayLength;
        uint idxLast = length - 1;

        assert(index <= idxLast);

        address addressToMove = PositionOwners[idxLast];

        PositionOwners[index] = addressToMove;
        Positions[addressToMove].arrayIndex = index;
        emit PositionIndexMoved(addressToMove, index);

        PositionOwners.pop();
    }

    /**
     * @dev Gets the entire debt and collateral for a borrower
     * @param _borrower The address of the borrower
     * @return debt The total debt of the borrower
     * @return coll The total collateral of the borrower
     * @return pendingStableDebtReward The pending stable debt reward
     * @return pendingCollateralReward The pending collateral reward
     */
    function _getEntireDebtAndColl(address _borrower) internal view
    returns (uint debt, uint coll, uint pendingStableDebtReward, uint pendingCollateralReward) {
        debt = Positions[_borrower].debt;
        coll = Positions[_borrower].coll;

        pendingStableDebtReward = _getPendingStableDebtReward(_borrower);
        pendingCollateralReward = _getPendingCollateralReward(_borrower);

        debt = debt + pendingStableDebtReward;
        coll = coll + pendingCollateralReward;
    }

    /**
     * @dev Calculates the pending collateral reward for a borrower
     * @param _borrower The address of the borrower
     * @return The pending collateral reward
     */
    function _getPendingCollateralReward(address _borrower) internal view returns (uint) {
        uint snapshotCollateral = rewardSnapshots[_borrower].Collateral;
        uint rewardPerUnitStaked = L_Collateral - snapshotCollateral;
        if ( rewardPerUnitStaked == 0 || Positions[_borrower].status != Status.active) {
            return 0;
        }
        uint stake = Positions[_borrower].stake;
        uint pendingCollateralReward = (stake * rewardPerUnitStaked) / DECIMAL_PRECISION;
        return pendingCollateralReward;
    }

    /**
     * @dev Calculates the pending stable debt reward for a borrower
     * @param _borrower The address of the borrower
     * @return The pending stable debt reward
     */
    function _getPendingStableDebtReward(address _borrower) internal view returns (uint) {
        uint snapshotStableDebt = rewardSnapshots[_borrower].stableDebt;
        uint rewardPerUnitStaked = L_StableDebt - snapshotStableDebt;

        if ( rewardPerUnitStaked == 0 || Positions[_borrower].status != Status.active) { return 0; }

        uint stake =  Positions[_borrower].stake;

        uint pendingStableDebtReward = (stake * rewardPerUnitStaked) / DECIMAL_PRECISION;

        return pendingStableDebtReward;
    }

    /**
     * @dev Calculates the current Individual Collateralization Ratio (ICR) for a borrower
     * @param _borrower The address of the borrower
     * @param _price The current price of the collateral
     * @return The current ICR
     */
    function _getCurrentICR(address _borrower, uint _price) internal view returns (uint) {
        (uint currentCollateral, uint currentStableDebt) = _getCurrentPositionAmounts(_borrower);

        uint ICR = StableMath._computeCR(currentCollateral, currentStableDebt, _price, collateralToken.decimals());
        return ICR;
    }

    /**
     * @dev Gets the current position amounts for a borrower
     * @param _borrower The address of the borrower
     * @return currentCollateral The current collateral amount
     * @return currentStableDebt The current stable debt amount
     */
    function _getCurrentPositionAmounts(address _borrower) internal view returns (uint, uint) {
        uint pendingCollateralReward = _getPendingCollateralReward(_borrower);
        uint pendingStableDebtReward = _getPendingStableDebtReward(_borrower);
        uint currentCollateral = Positions[_borrower].coll + pendingCollateralReward;
        uint currentStableDebt = Positions[_borrower].debt + pendingStableDebtReward;
        return (currentCollateral, currentStableDebt);
    }

    /**
     * @dev Gets the entire debt in the system
     * @return total The total debt in the system
     */
    function _getEntireDebt() internal view returns (uint total) {
        total = activePool.getStableDebt() + defaultPool.getStableDebt();
    }

    /**
     * @dev Gets the entire collateral in the system
     * @return total The total collateral in the system
     */
    function _getEntireCollateral() internal view returns (uint total) {
        total = activePool.getCollateral() + defaultPool.getCollateral();
    }

    /**
     * @dev Checks if the system is in recovery mode
     * @param _price The current price of the collateral
     * @return A boolean indicating if the system is in recovery mode
     */
    function _checkRecoveryMode(uint _price) internal view returns (bool) {
        uint CCR = collateralController.getCCR(address(collateralToken), collateralController.getVersion(address(this)));
        return _getTCR(_price) < CCR;
    }

    /**
     * @dev Calculates the Total Collateralization Ratio (TCR)
     * @param _price The current price of the collateral
     * @return TCR The Total Collateralization Ratio
     */
    function _getTCR(uint _price) internal view returns (uint TCR) {
        uint entirePositionColl = _getEntireCollateral();
        uint entirePositionDebt = _getEntireDebt();
        TCR = StableMath._computeCR(entirePositionColl, entirePositionDebt, _price, collateralToken.decimals());
    }
}
SortedPositions.sol 400 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

import "../../../interfaces/ISortedPositions.sol";
import "../../../interfaces/IPositionManager.sol";

/*
* A sorted doubly linked list with nodes sorted in descending order.
*
* Nodes map to active Positions in the system - the ID property is the address of a Position owner.
* Nodes are ordered according to their current nominal individual collateral ratio (NICR),
* which is like the ICR but without the price, i.e., just collateral / debt.
*
* The list optionally accepts insert position hints.
*
* NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active Positions
* change dynamically as liquidation events occur.
*
* The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active Positions,
* but maintains their order. A node inserted based on current NICR will maintain the correct position,
* relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not changed.
* Thus, Nodes remain sorted by current NICR.
*
* Nodes need only be re-inserted upon a Position operation - when the owner adds or removes collateral or debt
* to their position.
*
* The list is a modification of the following audited SortedDoublyLinkedList:
* https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol
*
*
* - Keys have been removed from nodes
*
* - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime.
*   The list relies on the property that ordering by ICR is maintained as the <COLLATERAL>:USD price varies.
*
* - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access
*/
contract SortedPositions is Ownable, ISortedPositions {
    string constant public NAME = "SortedPositions";
    address public positionControllerAddress;
    IPositionManager public positionManager;

    // Information for a node in the list
    struct Node {
        bool exists;
        address nextId;                  // Id of next node (smaller NICR) in the list
        address prevId;                  // Id of previous node (larger NICR) in the list
    }

    // Information for the list
    struct Data {
        address head;                        // Head of the list. Also the node in the list with the largest NICR
        address tail;                        // Tail of the list. Also the node in the list with the smallest NICR
        uint256 maxSize;                     // Maximum size of the list
        uint256 size;                        // Current size of the list
        mapping (address => Node) nodes;     // Track the corresponding ids for each node in the list
    }

    Data public data;

    function setParams(uint256 _size, address _positionManagerAddress, address _positionControllerAddress) external override onlyOwner {
        require(_size > 0, "SortedPositions: Size can't be zero");
        require(Address.isContract(_positionManagerAddress), "_positionManagerAddress is not a contract");
        require(Address.isContract(_positionControllerAddress), "_positionControllerAddress is not a contract");
        data.maxSize = _size;
        positionManager = IPositionManager(_positionManagerAddress);
        positionControllerAddress = _positionControllerAddress;
        emit PositionManagerAddressChanged(_positionManagerAddress);
        emit PositionControllerAddressChanged(_positionControllerAddress);
        renounceOwnership();
    }

    /*
     * @dev Add a node to the list
     * @param _id Node's id
     * @param _NICR Node's NICR
     * @param _prevId Id of previous node for the insert position
     * @param _nextId Id of next node for the insert position
     */

    function insert (address _id, uint256 _NICR, address _prevId, address _nextId) external override {
        IPositionManager positionManagerCached = positionManager;
        _requireCallerIsPCorPM(positionManagerCached);
        _insert(positionManagerCached, _id, _NICR, _prevId, _nextId);
    }

    function _insert(IPositionManager _positionManager, address _id, uint256 _NICR, address _prevId, address _nextId) internal {
        // List must not be full
        require(!isFull(), "SortedPositions: List is full");
        // List must not already contain node
        require(!contains(_id), "SortedPositions: List already contains the node");
        // Node id must not be null
        require(_id != address(0), "SortedPositions: Id cannot be zero");
        // NICR must be non-zero
        require(_NICR > 0, "SortedPositions: NICR must be positive");

        address prevId = _prevId;
        address nextId = _nextId;

        if (!_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
            // Sender's hint was not a valid insert position
            // Use sender's hint to find a valid insert position
            (prevId, nextId) = _findInsertPosition(_positionManager, _NICR, prevId, nextId);
        }

         data.nodes[_id].exists = true;

        if (prevId == address(0) && nextId == address(0)) {
            // Insert as head and tail
            data.head = _id;
            data.tail = _id;
        } else if (prevId == address(0)) {
            // Insert before `prevId` as the head
            data.nodes[_id].nextId = data.head;
            data.nodes[data.head].prevId = _id;
            data.head = _id;
        } else if (nextId == address(0)) {
            // Insert after `nextId` as the tail
            data.nodes[_id].prevId = data.tail;
            data.nodes[data.tail].nextId = _id;
            data.tail = _id;
        } else {
            // Insert at insert position between `prevId` and `nextId`
            data.nodes[_id].nextId = nextId;
            data.nodes[_id].prevId = prevId;
            data.nodes[prevId].nextId = _id;
            data.nodes[nextId].prevId = _id;
        }

        data.size = data.size + 1;
        emit NodeAdded(_id, _NICR);
    }

    function remove(address _id) external override {
        _requireCallerIsPositionManager();
        _remove(_id);
    }

    /*
     * @dev Remove a node from the list
     * @param _id Node's id
     */
    function _remove(address _id) internal {
        // List must contain the node
        require(contains(_id), "SortedPositions: List does not contain the id");

        if (data.size > 1) {
            // List contains more than a single node
            if (_id == data.head) {
                // The removed node is the head
                // Set head to next node
                data.head = data.nodes[_id].nextId;
                // Set prev pointer of new head to null
                data.nodes[data.head].prevId = address(0);
            } else if (_id == data.tail) {
                // The removed node is the tail
                // Set tail to previous node
                data.tail = data.nodes[_id].prevId;
                // Set next pointer of new tail to null
                data.nodes[data.tail].nextId = address(0);
            } else {
                // The removed node is neither the head nor the tail
                // Set next pointer of previous node to the next node
                data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;
                // Set prev pointer of next node to the previous node
                data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;
            }
        } else {
            // List contains a single node
            // Set the head and tail to null
            data.head = address(0);
            data.tail = address(0);
        }

        delete data.nodes[_id];
        data.size = data.size - 1;
        emit NodeRemoved(_id);
    }

    /*
     * @dev Re-insert the node at a new position, based on its new NICR
     * @param _id Node's id
     * @param _newNICR Node's new NICR
     * @param _prevId Id of previous node for the new insert position
     * @param _nextId Id of next node for the new insert position
     */
    function reInsert(address _id, uint256 _newNICR, address _prevId, address _nextId) external override {
        IPositionManager positionManagerCached = positionManager;

        _requireCallerIsPCorPM(positionManagerCached);
        // List must contain the node
        require(contains(_id), "SortedPositions: List does not contain the id");
        // NICR must be non-zero
        require(_newNICR > 0, "SortedPositions: NICR must be positive");

        // Remove node from the list
        _remove(_id);

        _insert(positionManagerCached, _id, _newNICR, _prevId, _nextId);
    }

    /*
     * @dev Checks if the list contains a node
     */
    function contains(address _id) public view override returns (bool) {
        return data.nodes[_id].exists;
    }

    /*
     * @dev Checks if the list is full
     */
    function isFull() public view override returns (bool) {
        return data.size == data.maxSize;
    }

    /*
     * @dev Checks if the list is empty
     */
    function isEmpty() public view override returns (bool) {
        return data.size == 0;
    }

    /*
     * @dev Returns the current size of the list
     */
    function getSize() external view override returns (uint256) {
        return data.size;
    }

    /*
     * @dev Returns the maximum size of the list
     */
    function getMaxSize() external view override returns (uint256) {
        return data.maxSize;
    }

    /*
     * @dev Returns the first node in the list (node with the largest NICR)
     */
    function getFirst() external view override returns (address) {
        return data.head;
    }

    /*
     * @dev Returns the last node in the list (node with the smallest NICR)
     */
    function getLast() external view override returns (address) {
        return data.tail;
    }

    /*
     * @dev Returns the next node (with a smaller NICR) in the list for a given node
     * @param _id Node's id
     */
    function getNext(address _id) external view override returns (address) {
        return data.nodes[_id].nextId;
    }

    /*
     * @dev Returns the previous node (with a larger NICR) in the list for a given node
     * @param _id Node's id
     */
    function getPrev(address _id) external view override returns (address) {
        return data.nodes[_id].prevId;
    }

    /*
     * @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR
     * @param _NICR Node's NICR
     * @param _prevId Id of previous node for the insert position
     * @param _nextId Id of next node for the insert position
     */
    function validInsertPosition(uint256 _NICR, address _prevId, address _nextId) external view override returns (bool) {
        return _validInsertPosition(positionManager, _NICR, _prevId, _nextId);
    }

    function _validInsertPosition(IPositionManager _positionManager, uint256 _NICR, address _prevId, address _nextId) internal view returns (bool) {
        if (_prevId == address(0) && _nextId == address(0)) {
            // `(null, null)` is a valid insert position if the list is empty
            return isEmpty();
        } else if (_prevId == address(0)) {
            // `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list
            return data.head == _nextId && _NICR >= _positionManager.getNominalICR(_nextId);
        } else if (_nextId == address(0)) {
            // `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list
            return data.tail == _prevId && _NICR <= _positionManager.getNominalICR(_prevId);
        } else {
            // `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and `_NICR` falls between the two nodes' NICRs
            return data.nodes[_prevId].nextId == _nextId &&
                   _positionManager.getNominalICR(_prevId) >= _NICR &&
                   _NICR >= _positionManager.getNominalICR(_nextId);
        }
    }

    /*
     * @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position
     * @param _positionManager PositionManager contract, passed in as param to save SLOAD’s
     * @param _NICR Node's NICR
     * @param _startId Id of node to start descending the list from
     */
    function _descendList(IPositionManager _positionManager, uint256 _NICR, address _startId) internal view returns (address, address) {
        // If `_startId` is the head, check if the insert position is before the head
        if (data.head == _startId && _NICR >= _positionManager.getNominalICR(_startId)) {
            return (address(0), _startId);
        }

        address prevId = _startId;
        address nextId = data.nodes[prevId].nextId;

        // Descend the list until we reach the end or until we find a valid insert position
        while (prevId != address(0) && !_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
            prevId = data.nodes[prevId].nextId;
            nextId = data.nodes[prevId].nextId;
        }

        return (prevId, nextId);
    }

    /*
     * @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position
     * @param _positionManager PositionManager contract, passed in as param to save SLOAD’s
     * @param _NICR Node's NICR
     * @param _startId Id of node to start ascending the list from
     */
    function _ascendList(IPositionManager _positionManager, uint256 _NICR, address _startId) internal view returns (address, address) {
        // If `_startId` is the tail, check if the insert position is after the tail
        if (data.tail == _startId && _NICR <= _positionManager.getNominalICR(_startId)) {
            return (_startId, address(0));
        }

        address nextId = _startId;
        address prevId = data.nodes[nextId].prevId;

        // Ascend the list until we reach the end or until we find a valid insertion point
        while (nextId != address(0) && !_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
            nextId = data.nodes[nextId].prevId;
            prevId = data.nodes[nextId].prevId;
        }

        return (prevId, nextId);
    }

    /*
     * @dev Find the insert position for a new node with the given NICR
     * @param _NICR Node's NICR
     * @param _prevId Id of previous node for the insert position
     * @param _nextId Id of next node for the insert position
     */
    function findInsertPosition(uint256 _NICR, address _prevId, address _nextId) external view override returns (address, address) {
        return _findInsertPosition(positionManager, _NICR, _prevId, _nextId);
    }

    function _findInsertPosition(IPositionManager _positionManager, uint256 _NICR, address _prevId, address _nextId) internal view returns (address, address) {
        address prevId = _prevId;
        address nextId = _nextId;

        if (prevId != address(0)) {
            if (!contains(prevId) || _NICR > _positionManager.getNominalICR(prevId)) {
                // `prevId` does not exist anymore or now has a smaller NICR than the given NICR
                prevId = address(0);
            }
        }

        if (nextId != address(0)) {
            if (!contains(nextId) || _NICR < _positionManager.getNominalICR(nextId)) {
                // `nextId` does not exist anymore or now has a larger NICR than the given NICR
                nextId = address(0);
            }
        }

        if (prevId == address(0) && nextId == address(0)) {
            // No hint - descend list starting from head
            return _descendList(_positionManager, _NICR, data.head);
        } else if (prevId == address(0)) {
            // No `prevId` for hint - ascend list starting from `nextId`
            return _ascendList(_positionManager, _NICR, nextId);
        } else if (nextId == address(0)) {
            // No `nextId` for hint - descend list starting from `prevId`
            return _descendList(_positionManager, _NICR, prevId);
        } else {
            // Descend list starting from `prevId`
            return _descendList(_positionManager, _NICR, prevId);
        }
    }

    // --- 'require' functions ---

    function _requireCallerIsPositionManager() internal view {
        require(msg.sender == address(positionManager), "SortedPositions: Caller is not the PositionManager");
    }

    function _requireCallerIsPCorPM(IPositionManager _positionManager) internal view {
        require(msg.sender == positionControllerAddress || msg.sender == address(_positionManager),
                "SortedPositions: Caller is neither PosCtrlr nor PosManager");
    }
}
DragonXPriceFeed.sol 46 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "./TitanXPriceFeed.sol";

/**
 * @title DragonXPriceFeed
 * @dev Extends TitanXPriceFeed to calculate DragonX price via DragonX -> TitanX -> ETH -> USD path
 */
contract DragonXPriceFeed is TitanXPriceFeed {
    address public immutable DRAGONX;

    constructor(
        address _uniFactoryAddress,
        address _dragonX,
        address _titanX,
        address _weth,
        address _positionController,
        address _collateralController,
        address[] memory _priceAggregatorAddresses
    ) TitanXPriceFeed(
    _uniFactoryAddress,
    _titanX,
    _weth,
    _positionController,
    _collateralController,
    _priceAggregatorAddresses
    ) {
        DRAGONX = _dragonX;
    }

    /**
     * @notice Overrides price calculation to use DragonX -> TitanX -> ETH -> USD path
     * @param secondsAgo Time period to look back for TWAP
     * @param currentUsdPerEth Current ETH/USD price
     * @return quote Final USD price of DragonX
     */
    function _getPrice(uint32 secondsAgo, uint currentUsdPerEth) internal view override returns (uint256 quote) {
        uint titanPerDragonX = _getQuoteFromPool(DRAGONX, TITANX, secondsAgo);
        uint ethPerTitanX = quoteTitanPrice(secondsAgo);
        uint ethPerDragonX = (titanPerDragonX * ethPerTitanX) / 1e18;
        uint usdPerDragonX = (ethPerDragonX * currentUsdPerEth) / 1e18;
        return usdPerDragonX;
    }
}
EthPriceFeed.sol 396 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../../../common/StableMath.sol";
import "../../../interfaces/IPriceFeed.sol";
import "../../../Guardable.sol";

/**
 * @title EthPriceFeed
 * @notice Abstract contract that manages and validates ETH price data from Chainlink oracles
 * @dev Implements circuit breaker functionality and price validation for Chainlink ETH price feed
 */
abstract contract EthPriceFeed is Guardable {
    /**
     * @dev Struct to store Chainlink oracle response data
     * @param roundId The round ID from Chainlink
     * @param answer The price answer from Chainlink
     * @param timestamp The timestamp of the price update
     * @param success Whether the Chainlink call was successful
     * @param decimals The number of decimals in the price data
     */
    struct ChainlinkResponse {
        uint80 roundId;
        int256 answer;
        uint256 timestamp;
        bool success;
        uint8 decimals;
    }

    // ======== STATE VARIABLES ========

    // Constants
    uint constant public TARGET_DIGITS = 18;  // Used to convert a price answer to an 18-digit precision uint
    uint constant public TIMEOUT = 14400;  // 4 hours: 60 * 60 * 4
    uint constant public MAX_ALLOWED_DEVIATION = 75e16; // 75% maximum allowed deviation setting
    uint constant public MIN_ALLOWED_DEVIATION = 20e16; // 20% minimum allowed deviation setting

    // Configurable deviation threshold
    uint public maxPriceDeviationFromPreviousRound = 3e17; // Default 30%

    // If true, immediately reverts if external call uses all gas instead of trying to set oracle as broken
    bool public oogGriefingProtectionEnabled = true;

    // Price feed state
    AggregatorV3Interface public priceAggregator;
    enum Status {chainlinkWorking, chainlinkBroken}
    Status public status;

    uint public lastGoodPrice;  // The last good price seen from the oracle by Usdx

    // ======== EVENTS ========

    event LastGoodPriceUpdated(uint _lastGoodPrice);
    event PriceFeedStatusChanged(Status newStatus);
    event MaxDeviationUpdated(uint oldDeviation, uint newDeviation);
    event GriefingProtectionEnabled(bool enabled);

    // ======== ERRORS ========

    error InsufficientGasForExternalCall(string target);

    // ======== EXTERNAL FUNCTIONS ========

    /**
     * @notice Updates the maximum allowed price deviation between rounds
     * @param newDeviation New maximum deviation value (with 18 decimal precision)
     * @dev Can only be called by guardian, must be between MIN_ALLOWED_DEVIATION and MAX_ALLOWED_DEVIATION
     */
    function setMaxPriceDeviation(uint newDeviation) external onlyGuardian {
        require(newDeviation >= MIN_ALLOWED_DEVIATION, "Deviation below minimum threshold");
        require(newDeviation <= MAX_ALLOWED_DEVIATION, "Deviation above maximum threshold");

        uint oldDeviation = maxPriceDeviationFromPreviousRound;
        maxPriceDeviationFromPreviousRound = newDeviation;

        emit MaxDeviationUpdated(oldDeviation, newDeviation);
    }

    function setGriefingProtection(bool enabled) external onlyGuardian {
        oogGriefingProtectionEnabled = enabled;
        emit GriefingProtectionEnabled(oogGriefingProtectionEnabled);
    }

    /**
     * @notice Fetches the current ETH price from Chainlink
     * @return Current ETH price with 18 decimal precision
     * @dev Reverts if the circuit breaker is active (status is chainlinkBroken), but if triggered, returns last good price.
     */
    function fetchEthPrice() public returns (uint) {
        require(status == Status.chainlinkWorking, "Chainlink Oracle Circuit Broken");
        ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse();

        if(_badChainlinkResponse(chainlinkResponse)) {
            return _tripBreakerAndReturnFinalLastGoodPrice();
        } else {
            if (_isFrozenOrDeviated(chainlinkResponse)) {
                return _tripBreakerAndReturnFinalLastGoodPrice();
            } else {
                return _storeChainlinkPrice(chainlinkResponse);
            }
        }
    }

    /**
     * @notice View function to get the current ETH price
     * @return Current ETH price or last good price if there are issues with Chainlink
     */
    function viewEthPrice() public view returns (uint) {
        ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse();

        if(_badChainlinkResponse(chainlinkResponse)) {
            return lastGoodPrice;
        } else {
            if (_isFrozenOrDeviated(chainlinkResponse)) {
                return lastGoodPrice;
            } else {
                return _scaleChainlinkPriceByDigits(uint256(chainlinkResponse.answer), chainlinkResponse.decimals);
            }
        }
    }

    /**
     * @notice Allows guardian to reset the circuit breaker and set a new price to continue from
     * @param newPrice The new price to set after resetting
     * @dev Can only be called by guardian when status is chainlinkBroken
     */
    function resetBreaker(uint newPrice) external onlyGuardian {
        require(status == Status.chainlinkBroken, "PriceFeed must be broken to intervene");
        _storePrice(newPrice);
        _changeStatus(Status.chainlinkWorking);
    }

    // ======== INTERNAL SETUP FUNCTIONS ========

    /**
     * @dev Sets up the price feed with Chainlink oracle addresses and initial price
     * @param _priceAggregatorAddresses Array of price aggregator addresses (only first one is used)
     */
    function _setAddresses(address[] memory _priceAggregatorAddresses) internal {
        priceAggregator = AggregatorV3Interface(_priceAggregatorAddresses[0]);
        status = Status.chainlinkWorking;

        ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse();
        ChainlinkResponse memory prevChainlinkResponse = _getPrevChainlinkResponse(chainlinkResponse.roundId, chainlinkResponse.decimals);

        require(!_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) && !_chainlinkIsFrozen(chainlinkResponse),
            "PriceFeed: Chainlink must be working and current");

        _storeChainlinkPrice(chainlinkResponse);
    }

    // ======== CHAINLINK INTERACTION FUNCTIONS ========

    /**
     * @dev Gets the current price data from Chainlink
     * @return chainlinkResponse Current Chainlink response
     */
    function _getCurrentChainlinkResponse() internal view returns (ChainlinkResponse memory chainlinkResponse) {
        uint256 gasBeforeDecimals = gasleft();

        // First, try to get current decimal precision:
        try priceAggregator.decimals() returns (uint8 decimals) {
            // If call to Chainlink succeeds, record the current decimal precision
            chainlinkResponse.decimals = decimals;
        } catch {
            // Require that enough gas was provided to prevent an OOG revert in the external call
            // causing a shutdown. Instead, just revert. Slightly conservative, as it includes gas used
            // in the check itself.
            if (
                gasleft() <= gasBeforeDecimals / 64 &&
                oogGriefingProtectionEnabled
            ) {revert InsufficientGasForExternalCall("decimals()");}

            // If call to Chainlink aggregator reverts, return a zero response with success = false
            return chainlinkResponse;
        }

        uint256 gasBeforeRoundData = gasleft();
        // Secondly, try to get latest price data:
        try priceAggregator.latestRoundData() returns
        (
            uint80 roundId,
            int256 answer,
            uint256 /* startedAt */,
            uint256 timestamp,
            uint80 /* answeredInRound */
        )
        {
            // If call to Chainlink succeeds, return the response and success = true
            chainlinkResponse.roundId = roundId;
            chainlinkResponse.answer = answer;
            chainlinkResponse.timestamp = timestamp;
            chainlinkResponse.success = true;
            return chainlinkResponse;
        } catch {
            // Require that enough gas was provided to prevent an OOG revert in the external call
            // causing a shutdown. Instead, just revert. Slightly conservative, as it includes gas used
            // in the check itself.
            if (
                gasleft() <= gasBeforeRoundData / 64 &&
                oogGriefingProtectionEnabled
            ) {revert InsufficientGasForExternalCall("latestRoundData()");}

            // If call to Chainlink aggregator reverts, return a zero response with success = false
            return chainlinkResponse;
        }
    }

    /**
     * @dev Gets the previous round's price data from Chainlink
     * @param _currentRoundId Current round ID
     * @param _currentDecimals Current decimal precision
     * @return prevChainlinkResponse Previous round's Chainlink response
     */
    function _getPrevChainlinkResponse(uint80 _currentRoundId, uint8 _currentDecimals) internal view returns (ChainlinkResponse memory prevChainlinkResponse) {
        /*
        * NOTE: Chainlink only offers a current decimals() value - there is no way to obtain the decimal precision used in a
        * previous round.  We assume the decimals used in the previous round are the same as the current round.
        */

        uint gasBefore = gasleft();
        // Try to get the price data from the previous round:
        try priceAggregator.getRoundData(_currentRoundId - 1) returns
        (
            uint80 roundId,
            int256 answer,
            uint256 /* startedAt */,
            uint256 timestamp,
            uint80 /* answeredInRound */
        )
        {
            // If call to Chainlink succeeds, return the response and success = true
            prevChainlinkResponse.roundId = roundId;
            prevChainlinkResponse.answer = answer;
            prevChainlinkResponse.timestamp = timestamp;
            prevChainlinkResponse.decimals = _currentDecimals;
            prevChainlinkResponse.success = true;
            return prevChainlinkResponse;
        } catch {
            // Require that enough gas was provided to prevent an OOG revert in the external call
            // causing a shutdown. Instead, just revert. Slightly conservative, as it includes gas used
            // in the check itself.
            if (
                gasleft() <= gasBefore / 64 &&
                oogGriefingProtectionEnabled
            ) {revert InsufficientGasForExternalCall("getRoundData()");}

            // If call to Chainlink aggregator reverts, return a zero response with success = false
            return prevChainlinkResponse;
        }
    }

    // ======== PRICE VALIDATION FUNCTIONS ========

    /**
     * @dev Checks if either current or previous Chainlink response is invalid
     * @param _currentResponse Current Chainlink response
     * @param _prevResponse Previous Chainlink response
     * @return bool True if either response is invalid
     */
    function _chainlinkIsBroken(ChainlinkResponse memory _currentResponse, ChainlinkResponse memory _prevResponse) internal view returns (bool) {
        return _badChainlinkResponse(_currentResponse) || _badChainlinkResponse(_prevResponse);
    }

    /**
     * @dev Validates a Chainlink response
     * @param _response Chainlink response to validate
     * @return bool True if the response is invalid
     */
    function _badChainlinkResponse(ChainlinkResponse memory _response) internal view returns (bool) {
        // Check for response call reverted
        if (!_response.success) {return true;}
        // Check for an invalid roundId that is 0
        if (_response.roundId == 0) {return true;}
        // Check for an invalid timeStamp that is 0, or in the future
        if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {return true;}
        // Check for non-positive price
        if (_response.answer <= 0) {return true;}

        return false;
    }

    /**
     * @dev Checks if the Chainlink response is too old
     * @param _response Chainlink response to check
     * @return bool True if the response is older than TIMEOUT
     */
    function _chainlinkIsFrozen(ChainlinkResponse memory _response) internal view returns (bool) {
        return (block.timestamp - _response.timestamp) > TIMEOUT;
    }

    /**
     * @dev Checks if price change between responses exceeds maximum allowed deviation
     * @param _currentResponse Current Chainlink response
     * @param _prevResponse Previous Chainlink response
     * @return bool True if price change exceeds maximum allowed deviation
     */
    function _chainlinkPriceChangeAboveMax(ChainlinkResponse memory _currentResponse, ChainlinkResponse memory _prevResponse) internal view returns (bool) {
        uint currentScaledPrice = _scaleChainlinkPriceByDigits(uint256(_currentResponse.answer), _currentResponse.decimals);
        uint prevScaledPrice = _scaleChainlinkPriceByDigits(uint256(_prevResponse.answer), _prevResponse.decimals);

        uint minPrice = StableMath._min(currentScaledPrice, prevScaledPrice);
        uint maxPrice = StableMath._max(currentScaledPrice, prevScaledPrice);

        /*
        * Use the larger price as the denominator:
        * - If price decreased, the percentage deviation is in relation to the the previous price.
        * - If price increased, the percentage deviation is in relation to the current price.
        */
        uint percentDeviation = ((maxPrice - minPrice) * StableMath.DECIMAL_PRECISION) / maxPrice;

        // Return true if price deviation exceeds the configured maximum
        return percentDeviation > maxPriceDeviationFromPreviousRound;
    }

    /**
     * @dev Checks if the current Chainlink response is frozen or has deviated too much from previous
     * @param current Current Chainlink response
     * @return bool True if price feed is frozen or has deviated too much
     */
    function _isFrozenOrDeviated(ChainlinkResponse memory current) internal view returns (bool) {
        ChainlinkResponse memory prev = _getPrevChainlinkResponse(current.roundId, current.decimals);
        return _badChainlinkResponse(prev) || _chainlinkIsFrozen(current) || _chainlinkPriceChangeAboveMax(current, prev);
    }

    // ======== PRICE STORAGE AND SCALING FUNCTIONS ========

    /**
     * @dev Scales Chainlink price to target decimal precision
     * @param _price Raw price from Chainlink
     * @param _answerDigits Decimal precision of the Chainlink price
     * @return uint Scaled price with TARGET_DIGITS precision
     */
    function _scaleChainlinkPriceByDigits(uint256 _price, uint256 _answerDigits) internal pure returns (uint) {
        /*
        * Convert the price returned by the Chainlink oracle to an 18-digit decimal for use by usdx.
        * At date of usdx launch, Chainlink uses an 8-digit price, but we also handle the possibility of
        * future changes.
        */
        uint price;
        if (_answerDigits >= TARGET_DIGITS) {
            // Scale the returned price value down to usdx's target precision
            price = _price / (10 ** (_answerDigits - TARGET_DIGITS));
        }
        else if (_answerDigits < TARGET_DIGITS) {
            // Scale the returned price value up to usdx's target precision
            price = _price * (10 ** (TARGET_DIGITS - _answerDigits));
        }
        return price;
    }

    /**
     * @dev Stores a new price and emits an event
     * @param _currentPrice New price to store
     */
    function _storePrice(uint _currentPrice) internal {
        lastGoodPrice = _currentPrice;
        emit LastGoodPriceUpdated(_currentPrice);
    }

    /**
     * @dev Stores a Chainlink price after scaling it to target precision
     * @param _chainlinkResponse Chainlink response containing the price to store
     * @return uint Stored scaled price
     */
    function _storeChainlinkPrice(ChainlinkResponse memory _chainlinkResponse) internal returns (uint) {
        uint scaledChainlinkPrice = _scaleChainlinkPriceByDigits(uint256(_chainlinkResponse.answer), _chainlinkResponse.decimals);
        _storePrice(scaledChainlinkPrice);
        return scaledChainlinkPrice;
    }

    // ======== STATUS MANAGEMENT FUNCTIONS ========

    /**
     * @dev Internal function to change the status of the price feed
     * @param _status New status to set
     */
    function _changeStatus(Status _status) internal {
        status = _status;
        emit PriceFeedStatusChanged(_status);
    }

    /**
     * @dev Internal function to trip the circuit breaker and return the last good price
     * @return Last good price before the circuit breaker was triggered
     */
    function _tripBreakerAndReturnFinalLastGoodPrice() internal returns (uint) {
        _changeStatus(Status.chainlinkBroken);
        return lastGoodPrice;
    }
}
TitanXPriceFeed.sol 489 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../../common/uniswap/TickMath.sol";
import "../../../common/uniswap/Oracle.sol";
import "../../../common/uniswap/PoolAddress.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "../../../common/Base.sol";
import "../../../common/StableMath.sol";
import "../../../interfaces/IPriceFeed.sol";
import "@openzeppelin/contracts/utils/math/SignedMath.sol";
import "@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol";
import "./EthPriceFeed.sol";

/**
 * @title TitanXPriceFeed
 * @notice Price feed implementation for TitanX token using Uniswap V3 TWAPs and Chainlink ETH price
 * @dev Extends EthPriceFeed to combine ETH/USD price with TitanX/ETH price from Uniswap
 */
contract TitanXPriceFeed is IPriceFeed, EthPriceFeed {
    /**
     * @dev Struct to store different timeframe prices for TitanX
     * @param spotUsd Current spot price in USD
     * @param sTwapUsd Short TWAP price in USD
     * @param lTwapUsd Long TWAP price in USD
     */
    struct PriceData {
        uint spotUsd;
        uint sTwapUsd;
        uint lTwapUsd;
    }

    // ======== STATE VARIABLES ========

    // Immutable addresses
    address public immutable positionController;
    address public immutable collateralController;
    address public immutable UNI_FACTORY;
    address public immutable TITANX;
    address public immutable WETH;

    // Mutable/set once address
    address public positionManager;

    // TWAP configuration
    uint32 public longTwapSeconds = 6 minutes;
    uint32 public shortTwapSeconds = 1 minutes;
    uint24 public constant FEE_TIER = 10000;   // 1%

    // Price averaging weights
    uint8 public shortTwapWeightInAverage = 1;
    uint8 public longTwapWeightInAverage = 2;
    // Limits the pull which the short TWAP can have on the long TWAP when calculating weighted average
    uint public maxShortTwapInfluencePCT = 10e16; // 10%

    // Circuit breaker and fee configuration
    bool public deviationTestingEnabled = true;
    bool public considerSpotInDeviationCircuitBreakerTesting = true;
    bool public advancedFeeEstimationEnabled = true;
    uint public maxOracleDeltaPCT = 5e16; // 5%
    uint public maxFeeProjection = 5e16; // 5%
    uint private constant DECIMAL_PRECISION = 1e18;

    // ======== MODIFIERS ========

    modifier onlyPC {
        require(msg.sender == positionController, "OnlyPC");
        _;
    }

    modifier onlyPM {
        require(msg.sender == positionManager, "OnlyPM");
        _;
    }

    modifier onlyPCorCC {
        require(msg.sender == positionController || msg.sender == collateralController, "OnlyPCorCC");
        _;
    }

    // ======== CONSTRUCTOR ========

    /**
     * @notice Initializes the TitanX price feed with required addresses and configuration
     * @param _uniFactoryAddress Uniswap V3 factory address
     * @param _titanX TitanX token address
     * @param _weth WETH token address
     * @param _positionController Position controller contract address
     * @param _collateralController Collateral controller contract address
     * @param _priceAggregatorAddresses Array of price aggregator addresses for ETH price feed
     */
    constructor(
        address _uniFactoryAddress,
        address _titanX,
        address _weth,
        address _positionController,
        address _collateralController,
        address[] memory _priceAggregatorAddresses
    ) {
        positionController = _positionController;
        collateralController = _collateralController;
        UNI_FACTORY = _uniFactoryAddress;
        TITANX = _titanX;
        WETH = _weth;

        address[] memory ethOracles = new address[](1);
        ethOracles[0] = _priceAggregatorAddresses[0];

        super._setAddresses(ethOracles);
    }

    // ======== EXTERNAL PRICE QUERY FUNCTIONS ========

    /**
     * @notice Fetches comprehensive price details including TWAPs and fee suggestions
     * @dev Warning: bypasses safety checks, do not use in protocol internals
     * @param utilizationPCT Current utilization percentage for fee calculation
     * @return PriceDetails struct containing price information and fee suggestions
     */
    function fetchPrice(uint utilizationPCT) public override view returns (PriceDetails memory) {
        PriceDetails memory details;
        PriceData memory priceData = PriceData(getPrice(0), getPrice(shortTwapSeconds), getPrice(longTwapSeconds));

        details.spotPrice = priceData.spotUsd;
        details.shortTwapPrice = priceData.sTwapUsd;
        details.longTwapPrice = priceData.lTwapUsd;

        details.lowestPrice = StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd);
        details.highestPrice = StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd);

        details.weightedAveragePrice =
            ((priceData.lTwapUsd * longTwapWeightInAverage) + (priceData.sTwapUsd * shortTwapWeightInAverage))
            /
            (longTwapWeightInAverage + shortTwapWeightInAverage);

        if (advancedFeeEstimationEnabled) {
            details.suggestedAdditiveFeePCT = _calculateSuggestedAdditiveFeePCT(utilizationPCT);
        } else {
            details.suggestedAdditiveFeePCT = 0;
        }

        return details;
    }

    /**
     * @notice Fetches the highest price between short-term and long-term TWAPs with an optional fee suggestion
     * @dev This function is restricted to the Position Manager (PM) contract only
     * @param loadIncrease The amount by which the current caller increased the load PCT
     * @param originationOrRedemptionLoadPCT The current percentage load (usage of points) for origination or redemption
     * @param testLiquidity Flag to determine if liquidity should be tested (not applicable to locked liquidity pools)
     * @param testDeviation Flag to check if price deviation checks should be performed during price data lookup
     * @return price The higher value between short-term and long-term TWAP prices in USD
     * @return suggestedAdditiveFeePCT Additional fee percentage suggested based on market conditions when advanced fee estimation is enabled, 0 otherwise
     */
    function fetchHighestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPM returns (uint price, uint suggestedAdditiveFeePCT) {
        PriceData memory priceData = _preCheckAndLookupPriceData(testDeviation);
        uint maxPrice = StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd);

        if (advancedFeeEstimationEnabled) {
            suggestedAdditiveFeePCT = _calculateSuggestedAdditiveFeePCT(originationOrRedemptionLoadPCT);
            return (maxPrice, suggestedAdditiveFeePCT);
        } else {
            return (maxPrice, 0);
        }
    }

    /**
     * @notice Fetches the lowest price between short-term and long-term TWAPs with an optional fee suggestion
     * @dev This function is restricted to the Position Controller (PC) contract only
     * @param loadIncrease The amount by which the current caller increased the load PCT
     * @param originationOrRedemptionLoadPCT The current percentage load (usage of points) for origination or redemption
     * @param testLiquidity Flag to determine if liquidity should be tested (not applicable to locked liquidity pools)
     * @param testDeviation Flag to check if price deviation checks should be performed during price data lookup
     * @return price The lower value between short-term and long-term TWAP prices in USD
     * @return suggestedAdditiveFeePCT Additional fee percentage suggested based on market conditions when advanced fee estimation is enabled, 0 otherwise
     */
    function fetchLowestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPC returns (uint price, uint suggestedAdditiveFeePCT) {
        PriceData memory priceData = _preCheckAndLookupPriceData(testDeviation);
        uint minPrice = StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd);

        if (advancedFeeEstimationEnabled) {
            suggestedAdditiveFeePCT = _calculateSuggestedAdditiveFeePCT(originationOrRedemptionLoadPCT);
            return (minPrice, suggestedAdditiveFeePCT);
        } else {
            return (minPrice, 0);
        }
    }

    /**
     * @notice Fetches the lowest TWAP price between short-term and long-term periods
     * @dev Restricted to Position Controller or Collateral Controller contracts only
     * @param testLiquidity Flag to determine if liquidity checks should be performed (not used)
     * @param testDeviation Flag to enable price deviation validation checks
     * @return price The lower value between short-term and long-term TWAP prices in USD
     */
    function fetchLowestPrice(bool testLiquidity, bool testDeviation) external override onlyPCorCC returns (uint price) {
        PriceData memory priceData = _preCheckAndLookupPriceData(testDeviation);
        price = StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd);
    }

    /**
     * @notice Calculates a weighted average price using short and long-term TWAPs
     * @dev Short TWAP's influence is limited by maxShortTwapInfluencePCT. Restricted to Position Manager contract only.
     * @param testLiquidity Flag to determine if liquidity checks should be performed (not used)
     * @param testDeviation Flag to enable price deviation validation checks
     * @return price Weighted average of long and short TWAP prices, calculated as:
     *         ((longTWAP * longWeight) + (shortTWAP * shortWeight)) / (longWeight + shortWeight)
     *         Weights are configurable parameters that determine the influence of each TWAP
     */
    function fetchWeightedAveragePrice(bool testLiquidity, bool testDeviation) external override onlyPM returns (uint price) {
        PriceData memory priceData = _preCheckAndLookupPriceData(testDeviation);

        uint weightedAverage =
            ((priceData.lTwapUsd * longTwapWeightInAverage) + (priceData.sTwapUsd * shortTwapWeightInAverage))
            / (longTwapWeightInAverage + shortTwapWeightInAverage);

        uint maxDeviation = (priceData.lTwapUsd * maxShortTwapInfluencePCT) / DECIMAL_PRECISION;
        uint upperBound = priceData.lTwapUsd + maxDeviation;
        uint lowerBound = priceData.lTwapUsd - maxDeviation;

        if (weightedAverage > upperBound) {
            return upperBound;
        } else if (weightedAverage < lowerBound) {
            return lowerBound;
        } else {
            return weightedAverage;
        }
    }

    // ======== UNISWAP PRICE QUERY FUNCTIONS ========

    /**
     * @notice Gets TitanX price for specified timeframe
     * @param secondsAgo Number of seconds ago to query price
     * @return quote Price in terms of ETH
     */
    function getPrice(uint32 secondsAgo) public view returns (uint256 quote) {
        return _getPrice(secondsAgo, super.viewEthPrice());
    }

    /**
     * @notice Queries Uniswap V3 pool for historical price data between any token pair
     * @dev Handles both current (slot0) and historical (oracle) price lookups
     * @param baseToken The token to price
     * @param quoteToken The token to price against
     * @param secondsAgo Time in the past to query price for
     * @return quote The exchange rate between tokens with 18 decimal precision
     */
    function _getQuoteFromPool(
        address baseToken,
        address quoteToken,
        uint32 secondsAgo
    ) internal view returns (uint256 quote) {
        address poolAddress = PoolAddress.computeAddress(
            UNI_FACTORY,
            PoolAddress.getPoolKey(baseToken, quoteToken, FEE_TIER)
        );
        uint32 oldestObservation = OracleLibrary.getOldestObservationSecondsAgo(poolAddress);

        // Limit to oldest observation
        if (oldestObservation < secondsAgo) {
            secondsAgo = oldestObservation;
        }

        uint160 sqrtPriceX96;
        if (secondsAgo == 0) {
            // Default to current price
            IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
            (sqrtPriceX96,,,,,,) = pool.slot0();
        } else {
            // Consult the Oracle Library for TWAPs
            (int24 arithmeticMeanTick,) = OracleLibrary.consult(poolAddress, secondsAgo);
            // Convert tick to sqrtPriceX96
            sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
        }

        return OracleLibrary.getQuoteForSqrtRatioX96(sqrtPriceX96, 1e18, baseToken, quoteToken);
    }

    /**
     * @notice Fetches the ETH/TitanX exchange rate from Uniswap V3
     * @dev Wrapper around _getQuoteFromPool specifically for TitanX/WETH pair
     * @param secondsAgo Number of seconds in the past to query the price
     * @return quote The amount of ETH per TitanX with 18 decimal precision
     */
    function quoteTitanPrice(uint32 secondsAgo) public view returns (uint256 quote) {
        return _getQuoteFromPool(TITANX, WETH, secondsAgo);
    }

    // ======== PRICE VALIDATION FUNCTIONS ========

    /**
     * @dev Central function for price data collection and validation
     * @param testDeviation If true, ensures price variations are within acceptable bounds
     * @return priceData Struct containing spot price, short TWAP, and long TWAP in USD
     *
     * Safety features:
     * - Optional deviation testing via circuit breaker
     * - Uses multiple timeframe TWAPs for price volatility estimation
     * - Integrates with ETH/USD Chainlink price feed for USD conversion
     */
    function _preCheckAndLookupPriceData(bool testDeviation) private returns (PriceData memory priceData) {
        uint256 currentUsdPerEth = super.fetchEthPrice();

        priceData = PriceData(
            _getPrice(0, currentUsdPerEth),
            _getPrice(shortTwapSeconds, currentUsdPerEth),
            _getPrice(longTwapSeconds, currentUsdPerEth)
        );

        if (testDeviation && deviationTestingEnabled) {
            require(
                _priceDeviationInSafeRange(priceData, considerSpotInDeviationCircuitBreakerTesting, maxOracleDeltaPCT),
                "Significant price deviation in progress"
            );
        }
    }

    /**
     * @dev Checks if price deviation is within acceptable range
     */
    function _priceDeviationInSafeRange(
        PriceData memory priceData,
        bool considerSpot,
        uint _maxOracleDeltaPCT
    ) internal pure returns (bool) {
        uint minPrice;
        uint maxPrice;

        if (considerSpot) {
            minPrice = StableMath._min(StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd), priceData.spotUsd);
            maxPrice = StableMath._max(StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd), priceData.spotUsd);
        } else {
            minPrice = StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd);
            maxPrice = StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd);
        }

        return _calcDiff(minPrice, maxPrice) <= _maxOracleDeltaPCT;
    }

    /**
    * @dev Calculates the percentage difference between max and min prices in 18 decimal precision
    */
    function _calcDiff(uint minPrice, uint maxPrice) internal pure returns (uint) {
        return ((maxPrice - minPrice) * DECIMAL_PRECISION) / maxPrice;
    }

    // ======== PRICE STORAGE FUNCTIONS ========

    /**
     * @dev Calculates and stores price for given timeframe
     */
    function _getPrice(uint32 secondsAgo, uint currentUsdPerEth) internal virtual view returns (uint256 quote) {
        uint ethPerTitanX = quoteTitanPrice(secondsAgo);
        uint256 usdPerTitan = (currentUsdPerEth * ethPerTitanX) / 1e18;
        return usdPerTitan;
    }

    // ======== FEE CALCULATION FUNCTIONS ========

    /**
     * @dev Calculates suggested additive fee percentage based on utilization
     */
    function _calculateSuggestedAdditiveFeePCT(uint utilizationPCT) internal view returns (uint) {
        return (utilizationPCT * maxFeeProjection) / DECIMAL_PRECISION;
    }

    // ======== ADMINISTRATIVE FUNCTIONS ========

    /**
     * @notice Sets the position manager address
     * @dev Can only be called once by guardian
     * @param pm Address of the position manager
     */
    function setPositionManager(address pm) external onlyGuardian {
        require(positionManager == address(0), "positionManager has already been set");
        positionManager = pm;
    }

    /**
     * @notice Sets the maximum percentage influence that short TWAP can have on the final price
     * @param _maxInfluencePCT Maximum percentage influence (18 decimals)
     */
    function setMaxShortTwapInfluence(uint _maxInfluencePCT) external onlyGuardian {
        require(_maxInfluencePCT <= DECIMAL_PRECISION, "Influence must be <= 100%");
        maxShortTwapInfluencePCT = _maxInfluencePCT;
    }

    /**
     * @notice Enables or disables deviation testing
     * @dev Controls whether price deviation checks are performed
     * @param isOn True to enable, false to disable
     */
    function setDeviationTesting(bool isOn) external onlyGuardian {
        deviationTestingEnabled = isOn;
    }

    /**
     * @notice Sets the weight for short TWAP in price averaging
     * @dev Weight must be greater than zero
     * @param weight New weight for short TWAP
     */
    function setShortTwapWeightInAverage(uint8 weight) external onlyGuardian {
        require(weight > 0, "Weight must be greater than zero");
        shortTwapWeightInAverage = weight;
    }

    /**
     * @notice Sets the weight for long TWAP in price averaging
     * @dev Weight must be greater than zero
     * @param weight New weight for long TWAP
     */
    function setLongTwapWeightInAverage(uint8 weight) external onlyGuardian {
        require(weight > 0, "Weight must be greater than zero");
        longTwapWeightInAverage = weight;
    }

    /**
     * @notice Sets whether to consider spot price in fee estimation
     * @param consider True to consider spot price, false to ignore it
     */
    function setConsiderSpotInFeeEstimation(bool consider) external onlyGuardian {
        considerSpotInDeviationCircuitBreakerTesting = consider;
    }

    /**
     * @notice Enables or disables advanced fee estimation
     * @param isOn True to enable, false to disable
     */
    function setAdvancedFeeEstimation(bool isOn) external onlyGuardian {
        advancedFeeEstimationEnabled = isOn;
    }

    /**
     * @notice Sets the duration for long TWAP calculation
     * @dev Must be greater than short TWAP seconds and greater than 0
     * @param _newLongTwapSeconds New duration in seconds for long TWAP
     */
    function setLongTwapSeconds(uint32 _newLongTwapSeconds) external onlyGuardian {
        require(_newLongTwapSeconds > 0, "Long TWAP seconds must be greater than 0");
        require(_newLongTwapSeconds > shortTwapSeconds, "Long TWAP must be greater than short TWAP");
        longTwapSeconds = _newLongTwapSeconds;
    }

    /**
     * @notice Sets the duration for short TWAP calculation
     * @dev Must be less than long TWAP seconds and greater than 0
     * @param _newShortTwapSeconds New duration in seconds for short TWAP
     */
    function setShortTwapSeconds(uint32 _newShortTwapSeconds) external onlyGuardian {
        require(_newShortTwapSeconds > 0, "Short TWAP seconds must be greater than 0");
        require(longTwapSeconds > _newShortTwapSeconds, "Long TWAP must be greater than short TWAP");
        shortTwapSeconds = _newShortTwapSeconds;
    }

    /**
     * @notice Sets the maximum allowed oracle price deviation
     * @dev Must be between 0.5% and 100%
     * @param _newMaxOracleDeltaPCT New maximum deviation percentage (18 decimals)
     */
    function setMaxOracleDelta(uint _newMaxOracleDeltaPCT) external onlyGuardian {
        uint HALF_PCT = 5e15; // 0.5%
        require(_newMaxOracleDeltaPCT >= HALF_PCT && _newMaxOracleDeltaPCT <= DECIMAL_PRECISION,
            "Must be between 0.5% - 100%");
        maxOracleDeltaPCT = _newMaxOracleDeltaPCT;
    }

    /**
     * @notice Sets the maximum fee projection for price calculations
     * @dev Must be between 1% and 100%
     * @param mfp New maximum fee projection percentage (18 decimals)
     */
    function setMaxFeeProjection(uint mfp) external onlyGuardian {
        require(mfp >= 1e16 && mfp <= 1e18, "Max Fee Projection must be between 1% and 100%");
        maxFeeProjection = mfp;
    }
}
WEthPriceFeed.sol 127 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../../interfaces/IPriceFeed.sol";
import "./EthPriceFeed.sol";

/**
 * @title WEthPriceFeed
 * @notice Price feed implementation for WETH token using Chainlink ETH price
 * @dev Extends EthPriceFeed to read ETH/USD
 */
contract WEthPriceFeed is IPriceFeed, EthPriceFeed {
    address public immutable positionController;
    address public immutable collateralController;

    // Mutable/set once address
    address public positionManager;

    modifier onlyPC {
        require(msg.sender == positionController, "OnlyPC");
        _;
    }

    modifier onlyPM {
        require(msg.sender == positionManager, "OnlyPM");
        _;
    }

    modifier onlyPCorCC {
        require(msg.sender == positionController || msg.sender == collateralController, "OnlyPCorCC");
        _;
    }

    // ======== CONSTRUCTOR ========

    /**
     * @notice Initializes price feed with required addresses and configuration
     * @param _positionController Position controller contract address
     * @param _collateralController Collateral controller contract address
     * @param _priceAggregatorAddress Price aggregator address for ETH price feed
     */
    constructor(
        address _positionController,
        address _collateralController,
        address _priceAggregatorAddress
    ) {
        positionController = _positionController;
        collateralController = _collateralController;

        address[] memory ethOracles = new address[](1);
        ethOracles[0] = _priceAggregatorAddress;

        _setAddresses(ethOracles);

        require(
            !_isFrozenOrDeviated(_getCurrentChainlinkResponse()),
            "Bad initial CL state"
        );
    }

    // ======== EXTERNAL PRICE QUERY FUNCTIONS ========

    /*
        Note: WEthPriceFeed acts as a thin adaptor for Chainlink ETH/USD price.
        As such, price deltas are not handled/represented.  All prices will reflect the CL price feed.
    */

    function fetchPrice(uint utilizationPCT) public override view returns (PriceDetails memory) {
        uint ethPrice = super.viewEthPrice();

        PriceDetails memory details;
        details.spotPrice = ethPrice;
        details.shortTwapPrice = ethPrice;
        details.longTwapPrice = ethPrice;
        details.lowestPrice = ethPrice;
        details.highestPrice = ethPrice;
        details.weightedAveragePrice = ethPrice;
        details.suggestedAdditiveFeePCT = 0;

        return details;
    }

    function fetchHighestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPM returns (uint price, uint suggestedAdditiveFeePCT) {
        return (super.fetchEthPrice(), 0);
    }

    function fetchLowestPriceWithFeeSuggestion(
        uint loadIncrease,
        uint originationOrRedemptionLoadPCT,
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPC returns (uint price, uint suggestedAdditiveFeePCT) {
        return (super.fetchEthPrice(), 0);
    }

    function fetchLowestPrice(
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPCorCC returns (uint price) {
        return super.fetchEthPrice();
    }

    function fetchWeightedAveragePrice(
        bool testLiquidity,
        bool testDeviation
    ) external override onlyPM returns (uint price) {
        return super.fetchEthPrice();
    }

    // ======== ADMINISTRATIVE FUNCTIONS ========

    /**
     * @notice Sets the position manager address
     * @dev Can only be called once by guardian
     * @param pm Address of the position manager
     */
    function setPositionManager(address pm) external onlyGuardian {
        require(positionManager == address(0), "positionManager has already been set");
        positionManager = pm;
    }
}
HintHelpers.sol 312 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";

import "../../common/StableMath.sol";
import "../../common/Base.sol";
import "../../interfaces/ICollateralController.sol";
import "../collateral/instance/PositionManager.sol";
import "./MultiTroveGetter.sol";

contract HintHelpers is Base, Ownable {
    ICollateralController public collateralController;

    function setAddresses(address _collateralController) external onlyOwner {
        collateralController = ICollateralController(_collateralController);
        renounceOwnership();
    }

    struct RedemptionVars {
        uint remainingStables;
        address currentPositionUser;
        uint netStableDebt;
        uint Collateral;
        uint newColl;
        uint newDebt;
        uint compositeDebt;
    }

    function getRedemptionHints(address asset, uint8 version, uint _stableAmount, uint _price, uint _maxIterations)
    external view returns (address firstRedemptionHint, uint partialRedemptionHintNICR, uint truncatedStableAmount) {

        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(asset, version);
        IPositionManager positionManager = collateral.positionManager;

        RedemptionVars memory vars;
        vars.remainingStables = _stableAmount;
        vars.currentPositionUser = collateral.sortedPositions.getLast();
        vars.currentPositionUser = _getNearestHealthyPosition(vars.currentPositionUser, positionManager, _price, collateral.sortedPositions, asset, version);

        firstRedemptionHint = vars.currentPositionUser;

        if (_maxIterations == 0) {
            _maxIterations = type(uint).max;
        }

        uint i;
        while (vars.currentPositionUser != address(0) && vars.remainingStables > 0 && i++ < _maxIterations) {
            vars.netStableDebt = _calcNetStableDebt(positionManager, asset, version, vars.currentPositionUser);

            if (vars.netStableDebt > vars.remainingStables) {
                if (vars.netStableDebt > MIN_NET_DEBT) {
                    uint maxRedeemableStables = StableMath._min(vars.remainingStables, vars.netStableDebt - MIN_NET_DEBT);

                    vars.Collateral = positionManager.getPositionColl(vars.currentPositionUser) + positionManager.getPendingCollateralReward(vars.currentPositionUser);
                    vars.newColl = vars.Collateral - ((maxRedeemableStables * DECIMAL_PRECISION) / _price);
                    vars.newDebt = vars.netStableDebt - maxRedeemableStables;

                    vars.compositeDebt = _calcCompDebt(vars.newDebt);
                    partialRedemptionHintNICR = StableMath._computeNominalCR(vars.newColl, vars.compositeDebt, collateral.asset.decimals());

                    vars.remainingStables = vars.remainingStables - maxRedeemableStables;
                }
                break;
            } else {
                vars.remainingStables = vars.remainingStables - vars.netStableDebt;
            }

            vars.currentPositionUser = collateral.sortedPositions.getPrev(vars.currentPositionUser);
        }

        truncatedStableAmount = _stableAmount - vars.remainingStables;
    }

    function _getNearestHealthyPosition(
        address currentPositionUser,
        IPositionManager positionManager,
        uint _price,
        ISortedPositions sortedPositionsCached,
        address asset,
        uint8 version
    ) private view returns (address) {
        uint MCR = collateralController.getMCR(asset, version);
        while (currentPositionUser != address(0) && positionManager.getCurrentICR(currentPositionUser, _price) < MCR) {
            currentPositionUser = sortedPositionsCached.getPrev(currentPositionUser);
        }
        return currentPositionUser;
    }

    function _calcCompDebt(uint netDebt) private view returns (uint) {
        return netDebt + GAS_COMPENSATION;
    }

    function _calcNetStableDebt(
        IPositionManager positionManager, address asset, uint8 version, address currentPositionUser
    ) private view returns (uint) {
        return (positionManager.getPositionDebt(currentPositionUser) - GAS_COMPENSATION) +
            positionManager.getPendingStableDebtReward(currentPositionUser);
    }

    /* getApproxHint() - return address of a Position that is, on average, (length / numTrials) positions away in the
    sortedPosition list from the correct insert position of the Position to be inserted.

    Note: The output address is worst-case O(n) positions away from the correct insert position, however, the function
    is probabilistic. Input can be tuned to guarantee results to a high degree of confidence, e.g:

    Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the output address will
    be <= sqrt(length) positions away from the correct insert position.
    */
    function getApproxHint(address asset, uint8 version, uint _CR, uint _numTrials, uint _inputRandomSeed)
    external
    view
    returns (address hintAddress, uint diff, uint latestRandomSeed)
    {
        ISortedPositions sortedPositions = collateralController.getCollateralInstance(asset, version).sortedPositions;
        IPositionManager positionManager = collateralController.getCollateralInstance(asset, version).positionManager;

        uint arrayLength = positionManager.getPositionOwnersCount();

        if (arrayLength == 0) {
            return (address(0), 0, _inputRandomSeed);
        }

        hintAddress = sortedPositions.getLast();
        diff = StableMath._getAbsoluteDifference(_CR, positionManager.getNominalICR(hintAddress));
        latestRandomSeed = _inputRandomSeed;

        uint i = 1;

        while (i < _numTrials) {
            latestRandomSeed = uint(keccak256(abi.encodePacked(latestRandomSeed)));

            uint arrayIndex = latestRandomSeed % arrayLength;
            address currentAddress = positionManager.getPositionFromPositionOwnersArray(arrayIndex);
            uint currentNICR = positionManager.getNominalICR(currentAddress);

            // check if abs(current - CR) > abs(closest - CR), and update closest if current is closer
            uint currentDiff = StableMath._getAbsoluteDifference(currentNICR, _CR);

            if (currentDiff < diff) {
                diff = currentDiff;
                hintAddress = currentAddress;
            }
            i++;
        }
    }

    struct AssetStats {
        bool sunset;
        uint256 availableLoanPoints;
        uint256 availableRedemptionPoints;
        uint256 numberOfPositions;
        uint256 debt;
        uint256 tvl;
        uint256 price;
        uint256 collateralTotal;
        uint256 TCR;
        bool recoveryMode;
        IMultiPositionGetter.CombinedPositionData[] riskiestPositions;
        uint256 MCR;
        uint256 CCR;
        uint256 debtCap;
        uint256 minBorrowingFeePct;
        uint256 maxBorrowingFeePct;
        uint256 minRedemptionFeePct;
        uint256 maxRedemptionFeePct;
        uint256 redemptionCooldownPeriod;
        uint256 redemptionGracePeriod;
        uint256 loanCooldownPeriod;
        uint256 loanGracePeriod;
        uint256 maxRedemptionPoints;
        uint256 redemptionRegenerationRate;
        uint256 maxLoanPoints;
        uint256 loanRegenerationRate;
        ICollateralController.BaseRateType baseRateType;
    }

    struct GlobalStats {
        uint256 totalNumberOfPositions;
        uint256 totalTvl;
        uint256 totalIssuedDebt;
        AssetStats[] assetStats;
        address guardian;
    }

    struct AssetData {
        address asset;
        uint8 version;
        uint256 numberOfPositions;
        uint256 price;
        uint256 collateralTotal;
        uint256 debt;
        uint256 tvl;
        uint256 TCR;
    }

    function getGlobalStats(address[] calldata assets, uint8[] calldata versions, address multiPositionGetter)
    external
    view
    returns (GlobalStats memory)
    {
        require(assets.length == versions.length, "Assets and versions length mismatch");

        GlobalStats memory stats;
        stats.assetStats = new AssetStats[](assets.length);
        stats.guardian = collateralController.getGuardian();

        for (uint i = 0; i < assets.length; i++) {
            AssetData memory data = getAssetData(assets[i], versions[i]);
            stats.assetStats[i] = computeAssetStats(data, multiPositionGetter);

            stats.totalNumberOfPositions += data.numberOfPositions;
            stats.totalTvl += data.tvl;
            stats.totalIssuedDebt += data.debt;
        }

        return stats;
    }

    function getAssetData(address asset, uint8 version) internal view returns (AssetData memory) {
        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(asset, version);

        uint256 numberOfPositions = collateral.positionManager.getPositionOwnersCount();
        uint256 price = collateral.priceFeed.fetchPrice(0).weightedAveragePrice;

        (uint256 collateralTotal, uint256 debt) = getTotalCollateralAndDebt(collateral.activePool, collateral.defaultPool);
        uint256 tvl = price * collateralTotal / 10**collateral.asset.decimals();
        uint256 TCR = StableMath._computeCR(collateralTotal, debt, price, collateral.asset.decimals());

        return AssetData({
            asset: asset,
            version: version,
            numberOfPositions: numberOfPositions,
            price: price,
            collateralTotal: collateralTotal,
            debt: debt,
            tvl: tvl,
            TCR: TCR
        });
    }

    function computeAssetStats(AssetData memory data, address multiPositionGetter) internal view returns (AssetStats memory) {
        ICollateralController.Collateral memory collateral = collateralController.getCollateralInstance(data.asset, data.version);
        ICollateralController.Settings memory settings = collateralController.getSettings(data.asset, data.version);

        return AssetStats({
            sunset: collateral.sunset,
            availableLoanPoints: collateralController.loanPointsAt(data.asset, data.version, block.timestamp),
            availableRedemptionPoints: collateralController.redemptionPointsAt(data.asset, data.version, block.timestamp),
            numberOfPositions: data.numberOfPositions,
            debt: data.debt,
            tvl: data.tvl,
            price: data.price,
            collateralTotal: data.collateralTotal,
            TCR: data.TCR,
            recoveryMode: data.TCR < settings.CCR,
            riskiestPositions: getRiskiestPositions(
                collateral.sortedPositions,
                PositionManager(address(collateral.positionManager)),
                10,
                multiPositionGetter
            ),
            MCR: settings.MCR,
            CCR: settings.CCR,
            debtCap: settings.debtCap,
            minBorrowingFeePct: settings.feeSettings.minBorrowingFeePct,
            maxBorrowingFeePct: settings.feeSettings.maxBorrowingFeePct,
            minRedemptionFeePct: settings.feeSettings.minRedemptionsFeePct,
            maxRedemptionFeePct: settings.feeSettings.maxRedemptionsFeePct,
            redemptionCooldownPeriod: settings.redemptionSettings.redemptionCooldownPeriod,
            redemptionGracePeriod: settings.redemptionSettings.redemptionGracePeriod,
            loanCooldownPeriod: settings.loanSettings.loanCooldownPeriod,
            loanGracePeriod: settings.loanSettings.loanGracePeriod,
            maxRedemptionPoints: settings.redemptionSettings.maxRedemptionPoints,
            redemptionRegenerationRate: settings.redemptionSettings.redemptionRegenerationRate,
            maxLoanPoints: settings.loanSettings.maxLoanPoints,
            loanRegenerationRate: settings.loanSettings.loanRegenerationRate,
            baseRateType: settings.feeSettings.baseRateType
        });
    }

    function getTotalCollateralAndDebt(IActivePool activePool, IDefaultPool defaultPool)
    internal
    view
    returns (uint256 collateralTotal, uint256 debt)
    {
        collateralTotal = activePool.getCollateral() + defaultPool.getCollateral();
        debt = activePool.getStableDebt() + defaultPool.getStableDebt();
    }

    function getRiskiestPositions(ISortedPositions sortedPositions, PositionManager positionManager, uint256 count, address mpg)
    internal
    view
    returns (IMultiPositionGetter.CombinedPositionData[] memory _positions)
    {
        return IMultiPositionGetter(mpg).getMultipleSortedPositions(
            positionManager,
            sortedPositions,
            -1,
            count
        );
    }

    function computeNominalCR(uint _coll, uint _debt, uint8 collateralDecimals) external pure returns (uint) {
        return StableMath._computeNominalCR(_coll, _debt, collateralDecimals);
    }

    function computeCR(uint _coll, uint _debt, uint _price, uint8 collateralDecimals) external pure returns (uint) {
        return StableMath._computeCR(_coll, _debt, _price, collateralDecimals);
    }
}
MultiTroveGetter.sol 115 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../collateral/instance/PositionManager.sol";

/*  Helper contract for grabbing Position data for the front end. Not part of the core Stable system. */
interface IMultiPositionGetter {
    struct CombinedPositionData {
        address owner;

        uint debt;
        uint coll;
        uint stake;

        uint snapshotCollateral;
        uint snapshotStableDebt;
    }

    function getMultipleSortedPositions(PositionManager positionManager, ISortedPositions sortedPositions, int _startIdx, uint _count)
    external view returns (CombinedPositionData[] memory _positions);
}

contract MultiPositionGetter is IMultiPositionGetter {
    function getMultipleSortedPositions(PositionManager positionManager, ISortedPositions sortedPositions, int _startIdx, uint _count)
        external override view returns (CombinedPositionData[] memory _positions)
    {
        uint startIdx;
        bool descend;

        if (_startIdx >= 0) {
            startIdx = uint(_startIdx);
            descend = true;
        } else {
            startIdx = uint(-(_startIdx + 1));
            descend = false;
        }

        uint sortedPositionsSize = sortedPositions.getSize();

        if (startIdx >= sortedPositionsSize) {
            _positions = new CombinedPositionData[](0);
        } else {
            uint maxCount = sortedPositionsSize - startIdx;

            if (_count > maxCount) {
                _count = maxCount;
            }

            if (descend) {
                _positions = _getMultipleSortedPositionsFromHead(positionManager, sortedPositions, startIdx, _count);
            } else {
                _positions = _getMultipleSortedPositionsFromTail(positionManager, sortedPositions, startIdx, _count);
            }
        }
    }

    function _getMultipleSortedPositionsFromHead(PositionManager positionManager, ISortedPositions sortedPositions, uint _startIdx, uint _count)
        internal view returns (CombinedPositionData[] memory _positions)
    {
        address currentPositionOwner = sortedPositions.getFirst();

        for (uint idx = 0; idx < _startIdx; ++idx) {
            currentPositionOwner = sortedPositions.getNext(currentPositionOwner);
        }

        _positions = new CombinedPositionData[](_count);

        for (uint idx = 0; idx < _count; ++idx) {
            _positions[idx].owner = currentPositionOwner;
            (
                _positions[idx].debt,
                _positions[idx].coll,
                _positions[idx].stake,
                /* status */,
                /* arrayIndex */
            ) = positionManager.Positions(currentPositionOwner);
            (
                _positions[idx].snapshotCollateral,
                _positions[idx].snapshotStableDebt
            ) = positionManager.rewardSnapshots(currentPositionOwner);

            currentPositionOwner = sortedPositions.getNext(currentPositionOwner);
        }
    }

    function _getMultipleSortedPositionsFromTail(PositionManager positionManager, ISortedPositions sortedPositions, uint _startIdx, uint _count)
        internal view returns (CombinedPositionData[] memory _positions)
    {
        address currentPositionOwner = sortedPositions.getLast();

        for (uint idx = 0; idx < _startIdx; ++idx) {
            currentPositionOwner = sortedPositions.getPrev(currentPositionOwner);
        }

        _positions = new CombinedPositionData[](_count);

        for (uint idx = 0; idx < _count; ++idx) {
            _positions[idx].owner = currentPositionOwner;
            (
                _positions[idx].debt,
                _positions[idx].coll,
                _positions[idx].stake,
                /* status */,
                /* arrayIndex */
            ) = positionManager.Positions(currentPositionOwner);
            (
                _positions[idx].snapshotCollateral,
                _positions[idx].snapshotStableDebt
            ) = positionManager.rewardSnapshots(currentPositionOwner);

            currentPositionOwner = sortedPositions.getPrev(currentPositionOwner);
        }
    }
}
DeterministicDeploymentProxy.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import "@openzeppelin/contracts/access/Ownable.sol";

contract DeterministicDeploymentProxy {
    function deploy(bytes memory _code, bytes32 _salt) external {
        address addr;
        assembly {
            addr := create2(0, add(_code, 0x20), mload(_code), _salt)
            if iszero(extcodesize(addr)) {
                revert(0, 0)
            }
        }
    }

    function transferOwnership(address addr) external {
        Ownable(addr).transferOwnership(msg.sender);
    }
}
ORXTester.sol 16 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../orx/minter/ORX.sol";

contract ORXTester is ORX {
    constructor(address stakingContract) ORX(stakingContract){}

    function testMint(address account, uint amount) external {
        _mint(account, amount);
    }

    function mint(address account, uint256 amount) public override {
        return _mint(account, amount);
    }
}
WETH.sol 81 lines
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.21;

/** Test WETH, do not deploy to prod.  Used during tests for UniswapV3 deployment, and as a stand-in mock deposit token */
contract WETH9 {
    string public name     = "Wrapped Ether";
    string public symbol   = "WETH";
    uint8  public decimals = 18;

    bool private failNextTransfer;

    event  Approval(address indexed src, address indexed guy, uint wad);
    event  Transfer(address indexed src, address indexed dst, uint wad);
    event  Deposit(address indexed dst, uint wad);
    event  Withdrawal(address indexed src, uint wad);

    mapping (address => uint)                       public  balanceOf;
    mapping (address => mapping (address => uint))  public  allowance;

    function setFailNextTransfer(bool _fail) external {
        failNextTransfer = _fail;
    }

    receive() external payable {
        deposit();
    }

    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }
    function mintTestTokens(uint amount) external {
        balanceOf[msg.sender] += amount;
        emit Deposit(msg.sender, amount);
    }
    function withdraw(uint wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        payable(msg.sender).transfer(wad);
        emit Withdrawal(msg.sender, wad);
    }

    function totalSupply() public view returns (uint) {
        return address(this).balance;
    }

    function approve(address guy, uint wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        emit Approval(msg.sender, guy, wad);
        return true;
    }

    function transfer(address dst, uint wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }

    function transferFrom(address src, address dst, uint wad)
    public
    returns (bool)
    {
        if (failNextTransfer) {
            failNextTransfer = false; // Reset for next call
            return false;
        }

        require(balanceOf[src] >= wad);

        if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }

        balanceOf[src] -= wad;
        balanceOf[dst] += wad;

        emit Transfer(src, dst, wad);

        return true;
    }
}
BackstopPoolIncentivesTester.sol 26 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../incentives/BackstopPoolIncentives.sol";

contract BackstopPoolIncentivesTester is BackstopPoolIncentives {
    function obtainFeeToken(uint _amount) external {
        feeToken.transfer(msg.sender, _amount);
    }

    function getCumulativeIssuanceFraction() external view returns (uint) {
        return _getCumulativeIssuanceFraction();
    }

    function turnOffRewards() external {
        backstopRewardsActive = false;
    }

    function unprotectedIssueFeeToken() external returns (uint) {
        uint latestTotalFeeTokensIssued = (incentivesSupplyCap * _getCumulativeIssuanceFraction()) / DECIMAL_PRECISION;
        uint issuance = latestTotalFeeTokensIssued - totalFeeTokensIssued;
        totalFeeTokensIssued = latestTotalFeeTokensIssued;
        return issuance;
    }
}
TestFeeToken.sol 14 lines
//SPDX-License-Identifier: Unlicense

pragma solidity 0.8.21;

import "../../orx/minter/ORX.sol";

contract TestFeeToken is ORX {
    constructor(address feeTokenStakingAddress, address _incentivesIssuance)
    ORX(feeTokenStakingAddress) {}

    function mint(address account, uint256 amount) public override {
        return _mint(account, amount);
    }
}
ActivePoolTester.sol 10 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/collateral/instance/ActivePool.sol";

contract ActivePoolTester is ActivePool {
    function unprotectedIncreaseStableDebt(uint _amount) external {
        stableDebt = stableDebt + _amount;
    }
}
BackstopPoolTester.sol 18 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/BackstopPool.sol";

contract BackstopPoolTester is BackstopPool {
    IActivePool public activePool;
    IDefaultPool public defaultPool;
    IPriceFeed public priceFeed;

    function setCurrentScale(uint128 _currentScale) external {
        currentScale = _currentScale;
    }

    function setTotalDeposits(uint _totalStableDeposits) external {
        totalStableDeposits = _totalStableDeposits;
    }
}
CDPManagerTester.sol 137 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../stable/collateral/instance/PositionManager.sol";

/* Tester contract inherits from PositionManager, and provides external functions
for testing the parent's internal functions. */

contract PositionManagerTester is PositionManager {
    constructor(
        address _collateralAsset,
        address _positionControllerAddress,
        address _activePoolAddress,
        address _defaultPoolAddress,
        address _backstopPoolAddress,
        address _gasPoolAddress,
        address _collSurplusPoolAddress,
        address _priceFeedAddress,
        address _stableTokenAddress,
        address _sortedPositionsAddress,
        address _feeTokenStakingAddress,
        address _collateralController
    ) PositionManager(
    _collateralAsset,
    _positionControllerAddress,
    _activePoolAddress,
    _defaultPoolAddress,
    _backstopPoolAddress,
    _gasPoolAddress,
    _collSurplusPoolAddress,
    _priceFeedAddress,
    _stableTokenAddress,
    _sortedPositionsAddress,
    _feeTokenStakingAddress,
    _collateralController
    ) {}

    function getPositionControllerAddress() external view returns (address) {
        return positionControllerAddress;
    }

    function computeICR(uint _coll, uint _debt, uint _price) external view returns (uint) {
        return StableMath._computeCR(_coll, _debt, _price, collateralToken.decimals());
    }

    function getCollGasCompensation(uint _coll) external pure returns (uint) {
        return _getCollGasCompensation(_coll);
    }

    function getCompositeDebt(uint _debt) external view returns (uint) {
        return _debt + GAS_COMPENSATION;
    }

    function callGetRedemptionFee(uint _CollateralDrawn, uint suggestedAdditiveFeePCT) external view returns (uint) {
        return _getRedemptionFee(_CollateralDrawn, suggestedAdditiveFeePCT);
    }

    function getActualDebtFromComposite(uint _debtVal) external view returns (uint) {
        return _debtVal - GAS_COMPENSATION;
    }

    function testCallToRegenerateRedemptions(uint256 _stableAmount) external {
        require(address(collateralController) != address(0), "CollateralController not set");
        collateralController.regenerateAndConsumeRedemptionPoints(_stableAmount);
    }

    function testCallToConsumeLoanPoints(address asset, uint8 version, uint256 _stableAmount) external {
        require(address(collateralController) != address(0), "CollateralController not set");
        collateralController.regenerateAndConsumeLoanPoints(asset, version, _stableAmount);
    }

    function baseRate() external override view returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if(brt == ICollateralController.BaseRateType.Global) {
            return collateralController.getBaseRate();
        } else {
            return _baseRate;
        }
    }

    function minutesPassedSinceLastFeeOp() external view returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if (brt == ICollateralController.BaseRateType.Global) {
            return collateralController.minutesPassedSinceLastFeeOp();
        } else {
            return _minutesPassedSinceLastFeeOp();
        }
    }

    function setLastFeeOpTimeToNow() external {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if (brt == ICollateralController.BaseRateType.Global) {
            (bool success,) = address(collateralController).call(
                abi.encodeWithSignature("setLastFeeOpTimeToNow()")
            );
            require(success, "setLastFeeOpTimeToNow failed");
        } else {
            _lastFeeOperationTime = block.timestamp;
        }
    }

    function setBaseRate(uint __baseRate) external {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if (brt == ICollateralController.BaseRateType.Global) {
            (bool success,) = address(collateralController).call(
                abi.encodeWithSignature("setBaseRate(uint256)", __baseRate)
            );
            require(success, "setBaseRate failed");
        } else {
            _baseRate = __baseRate;
        }
    }

    function unprotectedDecayBaseRateFromBorrowing() external returns (uint) {
        ICollateralController.BaseRateType brt = collateralController.getBaseRateType();
        if (brt == ICollateralController.BaseRateType.Global) {
            (bool success, bytes memory returnData) = address(collateralController).call(
                abi.encodeWithSignature("unprotectedDecayBaseRateFromBorrowing()")
            );
            require(success, "unprotectedDecayBaseRateFromBorrowing failed");

            // Decode return value
            return abi.decode(returnData, (uint256));
        } else {
            _baseRate = _calcDecayedBaseRate();
            assert(_baseRate >= 0 && _baseRate <= DECIMAL_PRECISION);

            _updateLastFeeOpTime();
            return _baseRate;
        }
    }

    function unprotectedUpdateLastFeeOpTime() external {
        _updateLastFeeOpTime();
    }
}
CollateralControllerTester.sol 35 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/collateral/instance/ActivePool.sol";
import "../../stable/collateral/CollateralControllerImpl.sol";

contract CollateralControllerTester is CollateralControllerImpl {
    function setPositionController(address pc) external {
        positionControllerAddress = pc;
    }

    function setBackstopWithdrawalValidation(address asset, uint8 version, bool excluded) external {
        excludedFromBackstopWithdrawalChecks[asset][version] = excluded;
    }

    function setLastFeeOpTimeToNow() external {
        _lastFeeOperationTime = block.timestamp;
    }

    function setBaseRate(uint __baseRate) external {
        _baseRate = __baseRate;
    }

    function getBaseRateType(uint __baseRate) external {
        _baseRate = __baseRate;
    }

    function unprotectedDecayBaseRateFromBorrowing() external returns (uint) {
        _baseRate = _calcDecayedBaseRate();
        assert(_baseRate >= 0 && _baseRate <= DECIMAL_PRECISION);

        _updateLastFeeOpTime();
        return _baseRate;
    }
}
proxy.sol 217 lines
// SPDX-License-Identifier: MIT

// From: https://etherscan.io/address/0xa26e15c895efc0616177b7c1e7270a4c7d51c997#code
/**
 *Submitted for verification at Etherscan.io on 2018-06-22
*/

// proxy.sol - execute actions atomically through the proxy's identity

// Copyright (C) 2017  DappHub, LLC

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity 0.8.21;

abstract contract DSAuthority {
    function canCall(
        address src, address dst, bytes4 sig
    ) virtual public view returns (bool);
}

contract DSAuthEvents {
    event LogSetAuthority (address indexed authority);
    event LogSetOwner     (address indexed owner);
}

contract DSAuth is DSAuthEvents {
    DSAuthority  public  authority;
    address      public  owner;

    constructor() {
        owner = msg.sender;
        emit LogSetOwner(msg.sender);
    }

    function setOwner(address owner_)
        public
        auth
    {
        owner = owner_;
        emit LogSetOwner(owner);
    }

    function setAuthority(DSAuthority authority_)
        public
        auth
    {
        authority = authority_;
        emit LogSetAuthority(address(authority));
    }

    modifier auth {
        require(isAuthorized(msg.sender, msg.sig));
        _;
    }

    function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
        if (src == address(this)) {
            return true;
        } else if (src == owner) {
            return true;
        } else if (authority == DSAuthority(address(0))) {
            return false;
        } else {
            return authority.canCall(src, address(this), sig);
        }
    }
}

contract DSNote {
    event LogNote(
        bytes4   indexed  sig,
        address  indexed  guy,
        bytes32  indexed  foo,
        bytes32  indexed  bar,
        uint              wad,
        bytes             fax
    ) anonymous;

    modifier note {
        bytes32 foo;
        bytes32 bar;

        assembly {
            foo := calldataload(4)
            bar := calldataload(36)
        }

        emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);

        _;
    }
}

// DSProxy
// Allows code execution using a persistant identity This can be very
// useful to execute a sequence of atomic actions. Since the owner of
// the proxy can be changed, this allows for dynamic ownership models
// i.e. a multisig
contract DSProxy is DSAuth, DSNote {
    DSProxyCache public cache;  // global cache for contracts

    constructor(address _cacheAddr) {
        require(setCache(_cacheAddr));
    }

    receive() external payable {}

    // use the proxy to execute calldata _data on contract _code
    function execute(bytes memory _code, bytes memory _data)
        public
        payable
        returns (address target, bytes32 response)
    {
        target = cache.read(_code);
        if (target == address(0x0)) {
            // deploy contract & store its address in cache
            target = cache.write(_code);
        }

        response = execute(target, _data);
    }

    function execute(address _target, bytes memory _data)
        public
        auth
        note
        payable
        returns (bytes32 response)
    {
        require(_target != address(0x0));

        // call contract in current context
        assembly {
            let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 32)
            response := mload(0)      // load delegatecall output
            switch iszero(succeeded)
            case 1 {
                // throw if delegatecall failed
                revert(0, 0)
            }
        }
    }

    //set new cache
    function setCache(address _cacheAddr) public payable auth note returns (bool) {
        require(_cacheAddr != address(0x0));        // invalid cache address
        cache = DSProxyCache(_cacheAddr);  // overwrite cache
        return true;
    }
}

// DSProxyFactory
// This factory deploys new proxy instances through build()
// Deployed proxy addresses are logged
contract DSProxyFactory {
    event Created(address indexed sender, address indexed owner, address proxy, address cache);
    mapping(address=>bool) public isProxy;
    DSProxyCache public cache = new DSProxyCache();

    // deploys a new proxy instance
    // sets owner of proxy to caller
    function build() public returns (DSProxy proxy) {
        proxy = build(msg.sender);
    }

    // deploys a new proxy instance
    // sets custom owner of proxy
    function build(address owner) public returns (DSProxy proxy) {
        proxy = new DSProxy(address(cache));
        emit Created(msg.sender, owner, address(proxy), address(cache));
        proxy.setOwner(owner);
        isProxy[address(proxy)] = true;
    }
}

// DSProxyCache
// This global cache stores addresses of contracts previously deployed
// by a proxy. This saves gas from repeat deployment of the same
// contracts and eliminates blockchain bloat.

// By default, all proxies deployed from the same factory store
// contracts in the same cache. The cache a proxy instance uses can be
// changed.  The cache uses the sha3 hash of a contract's bytecode to
// lookup the address
contract DSProxyCache {
    mapping(bytes32 => address) cache;

    function read(bytes memory _code) public view returns (address) {
        bytes32 hash = keccak256(_code);
        return cache[hash];
    }

    function write(bytes memory _code) public returns (address target) {
        assembly {
            target := create(0, add(_code, 0x20), mload(_code))
            switch iszero(extcodesize(target))
            case 1 {
                // throw if contract failed to deploy
                revert(0, 0)
            }
        }
        bytes32 hash = keccak256(_code);
        cache[hash] = target;
    }
}
DefaultPoolTester.sol 10 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/collateral/instance/DefaultPool.sol";

contract DefaultPoolTester is DefaultPool {
    function unprotectedIncreaseStableDebt(uint _amount) external {
        stableDebt = stableDebt + _amount;
    }
}
DragonXPriceFeedTester.sol 34 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/collateral/oracle/TitanXPriceFeed.sol";
import "../../stable/collateral/oracle/DragonXPriceFeed.sol";

contract DragonXPriceFeedTester is DragonXPriceFeed {
    constructor(
        address uniFactoryAddress,
        address dragonx,
        address titanx,
        address weth,
        address _positionController,
        address _collateralController,
        address[] memory ethOracles
    ) DragonXPriceFeed(
        uniFactoryAddress,
        dragonx,
        titanx,
        weth,
        _positionController,
        _collateralController,
        ethOracles
    ) {}

    function getOldestObservation(uint32 _secondsAgo) external view returns (uint32 secondsAgo) {
        address poolAddress = PoolAddress.computeAddress(
            UNI_FACTORY,
            PoolAddress.getPoolKey(TITANX, WETH, FEE_TIER)
        );

        return OracleLibrary.getOldestObservationSecondsAgo(poolAddress);
    }
}
EmptyContract.sol 13 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../interfaces/ICanReceiveCollateral.sol";

contract EmptyContract is ICanReceiveCollateral {
    function forward(address _dest, bytes calldata _data) external {
        (bool success, bytes memory returnData) = _dest.call(_data);
        require(success, string(returnData));
    }

    function receiveCollateral(address asset, uint amount) external override {}
}
FeeTokenStakingTester.sol 10 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../orx/staking/FeeTokenStaking.sol";

contract FeeTokenStakingTester is FeeTokenStaking {
    function requireCallerIsPositionManager() external view {
        _requireCallerIsPositionManager(address(0), 0);
    }
}
FeeTokenTester.sol 36 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../orx/minter/ORX.sol";

contract FeeTokenTester is ORX {
    constructor(address feeTokenStakingAddress, address _incentivesIssuance)
    ORX(feeTokenStakingAddress) {
        _mint(msg.sender, 100_000_000e18);
    }

    function unprotectedMint(address account, uint256 amount) external {
        // No check for the caller here

        _mint(account, amount);
    }

    function unprotectedSendToFeeTokenStaking(address _sender, uint256 _amount) external {
        _transfer(_sender, orxStakingAddress, _amount);
    }

    function callInternalApprove(address owner, address spender, uint256 amount) external returns (bool) {
        _approve(owner, spender, amount);
    }

    function callInternalTransfer(address sender, address recipient, uint256 amount) external returns (bool) {
        _transfer(sender, recipient, amount);
    }

    function getChainId() external view returns (uint256 chainID) {
        //return _chainID(); // it’s private
        assembly {
            chainID := chainid()
        }
    }
}
FunctionCaller.sol 43 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../interfaces/IPositionManager.sol";
import "../../interfaces/ISortedPositions.sol";
import "../../interfaces/IPriceFeed.sol";

/* Wrapper contract - used for calculating gas of read-only and internal functions.
Not part of the Stable.sol application. */

contract FunctionCaller {
    IPositionManager public positionManager;
    address public positionManagerAddress;

    ISortedPositions public sortedPositions;
    address public sortedPositionsAddress;

    IPriceFeed public priceFeed;
    address public priceFeedAddress;

    function setPositionManagerAddress(address _positionManagerAddress) external {
        positionManagerAddress = _positionManagerAddress;
        positionManager = IPositionManager(_positionManagerAddress);
    }
    
    function setSortedPositionsAddress(address _sortedPositionsAddress) external {
        positionManagerAddress = _sortedPositionsAddress;
        sortedPositions = ISortedPositions(_sortedPositionsAddress);
    }

     function setPriceFeedAddress(address _priceFeedAddress) external {
        priceFeedAddress = _priceFeedAddress;
        priceFeed = IPriceFeed(_priceFeedAddress);
    }

    function positionManager_getCurrentICR(address _address, uint _price) external view returns (uint) {
        return positionManager.getCurrentICR(_address, _price);
    }

    function sortedPositions_findInsertPosition(uint _NICR, address _prevId, address _nextId) external view returns (address, address) {
        return sortedPositions.findInsertPosition(_NICR, _prevId, _nextId);
    }
}
MockAggregator.sol 113 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract MockAggregator is AggregatorV3Interface {
    
    // storage variables to hold the mock data
    uint8 private decimalsVal = 8;
    int private price;
    int private prevPrice;
    uint private updateTime;
    uint private prevUpdateTime;

    uint80 private latestRoundId;
    uint80 private prevRoundId;

    bool latestRevert;
    bool prevRevert;
    bool decimalsRevert;

    // --- Functions ---

    function setDecimals(uint8 _decimals) external {
        decimalsVal = _decimals;
    }

    function setPrice(int _price) external {
        price = _price;
    }

    function setPrevPrice(int _prevPrice) external {
        prevPrice = _prevPrice;
    }

    function setPrevUpdateTime(uint _prevUpdateTime) external {
        prevUpdateTime = _prevUpdateTime;
    }

    function setUpdateTime(uint _updateTime) external  {
        updateTime = _updateTime;
    }

    function setLatestRevert() external  {
        latestRevert = !latestRevert;
    }

    function setPrevRevert() external  {
        prevRevert = !prevRevert;
    }

    function setDecimalsRevert() external {
        decimalsRevert = !decimalsRevert;
    }

    function setLatestRoundId(uint80 _latestRoundId) external {
        latestRoundId = _latestRoundId;
    }

      function setPrevRoundId(uint80 _prevRoundId) external {
        prevRoundId = _prevRoundId;
    }
    

    // --- Getters that adhere to the AggregatorV3 interface ---

    function decimals() external override view returns (uint8) {
        if (decimalsRevert) {require(1== 0, "decimals reverted");}

        return decimalsVal;
    }

    function latestRoundData()
        external
        override
        view
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) 
    {    
        if (latestRevert) { require(1== 0, "latestRoundData reverted");}

        return (latestRoundId, price, 0, updateTime, 0); 
    }

    function getRoundData(uint80)
    external
    override 
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    ) {
        if (prevRevert) {require( 1== 0, "getRoundData reverted");}

        return (prevRoundId, prevPrice, 0, updateTime, 0);
    }

    function description() external override pure returns (string memory) {
        return "";
    }
    function version() external override pure returns (uint256) {
        return 1;
    }
}
MockChainlinkETHUSD.sol 201 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MockChainlinkETHUSD {
    int256 private _answer;
    uint256 private _timestamp;
    uint80 private _roundId;
    uint8 private constant _decimals = 8; // ETH/USD feed uses 8 decimals
    string private constant _description = "ETH / USD";
    uint256 private constant _version = 4;

    bool private _oogInDecimals;
    bool private _oogInLatestRoundData;
    bool private _oogInGetRoundData;

    bool private _violateRuntimeStateModificationConstraint;
    uint8 private _stateCounter;

    mapping(uint80 => RoundData) private _roundData;

    struct RoundData {
        int256 answer;
        uint256 startedAt;
        uint256 updatedAt;
        uint80 answeredInRound;
        bool exists;
    }

    event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
    event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);

    constructor(int256 initialPrice) {
        _roundId = 2;
        _answer = initialPrice;
        _timestamp = block.timestamp;

        // Set round 1
        _roundData[1] = RoundData({
            answer: initialPrice,
            startedAt: block.timestamp,
            updatedAt: block.timestamp,
            answeredInRound: 1,
            exists: true
        });

        // Set round 2 (latest)
        _roundData[2] = RoundData({
            answer: initialPrice,
            startedAt: block.timestamp + 12,
            updatedAt: block.timestamp + 12,
            answeredInRound: 2,
            exists: true
        });
    }

    function oogInDecimals(bool shouldOog) external {
        _oogInDecimals = shouldOog;
    }

    function oogInLatestRoundData(bool shouldOog) external {
        _oogInLatestRoundData = shouldOog;
    }

    function oogInGetRoundData(bool shouldOog) external {
        _oogInGetRoundData = shouldOog;
    }

    function violateRuntimeStateModificationConstraint(bool shouldViolate) external {
        _violateRuntimeStateModificationConstraint = shouldViolate;
    }

    // AggregatorInterface functions
    function latestAnswer() external view returns (int256) {
        return _answer;
    }

    function latestTimestamp() external view returns (uint256) {
        return _timestamp;
    }

    function latestRound() external view returns (uint256) {
        return _roundId;
    }

    function getAnswer(uint256 roundId) external view returns (int256) {
        if (!_roundData[uint80(roundId)].exists) return 0;
        return _roundData[uint80(roundId)].answer;
    }

    function getTimestamp(uint256 roundId) external view returns (uint256) {
        if (!_roundData[uint80(roundId)].exists) return 0;
        return _roundData[uint80(roundId)].updatedAt;
    }

    // AggregatorV3Interface functions
    function decimals() external view returns (uint8) {
        if(_oogInDecimals) {
            for(uint i; i < type(uint).max; i++) { /* trigger OOG */}
            return 0;
        } else {
            return _decimals;
        }
    }

    function description() external pure returns (string memory) {
        return _description;
    }

    function version() external pure returns (uint256) {
        return _version;
    }

    function getRoundData(uint80 roundId) external returns (
        uint80 id,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) {
        if(_oogInGetRoundData) {
            for(uint i; i < type(uint).max; i++) { /* trigger OOG */}
        }

        if (_violateRuntimeStateModificationConstraint) {
            // interface we reference through is a view function, but we implement it here as non-view/mutable
            _stateCounter++;
        }

        require(_roundData[roundId].exists, "No data present");
        RoundData memory data = _roundData[roundId];
        return (
            roundId,
            data.answer,
            data.startedAt,
            data.updatedAt,
            data.answeredInRound
        );
    }

    function latestRoundData() external view returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) {
        if(_oogInLatestRoundData) {
            for(uint i; i < type(uint).max; i++) { /* trigger OOG */}
        }

        require(_roundData[_roundId].exists, "No data present");
        RoundData memory data = _roundData[_roundId];
        return (
            _roundId,
            _answer,
            data.startedAt,
            _timestamp,
            _roundId
        );
    }

    // Test helper functions
    function updateAnswer(int256 newAnswer) external {
        _answer = newAnswer;
        _timestamp = block.timestamp;
        _roundId++;

        _roundData[_roundId] = RoundData({
            answer: newAnswer,
            startedAt: block.timestamp,
            updatedAt: block.timestamp,
            answeredInRound: _roundId,
            exists: true
        });

        emit AnswerUpdated(newAnswer, _roundId, block.timestamp);
        emit NewRound(_roundId, msg.sender, block.timestamp);
    }

    function updateAnswerWithRound(int256 newAnswer, uint80 newRoundId) external {
        _answer = newAnswer;
        _timestamp = block.timestamp;
        _roundId = newRoundId;

        _roundData[newRoundId] = RoundData({
            answer: newAnswer,
            startedAt: block.timestamp,
            updatedAt: block.timestamp,
            answeredInRound: newRoundId,
            exists: true
        });

        emit AnswerUpdated(newAnswer, newRoundId, block.timestamp);
        emit NewRound(newRoundId, msg.sender, block.timestamp);
    }

    function setTimestamp(uint256 timestamp) external {
        _timestamp = timestamp;
        _roundData[_roundId].updatedAt = timestamp;
    }
}
MockChainlinkPriceFeed.sol 34 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

/**
 * @title MockChainlinkPriceFeed
 * @notice Mock Chainlink price feed for testing
 */
contract MockChainlinkPriceFeed {
    int256 private price;
    uint8 private _decimals;

    constructor(int256 _price, uint8 decimals_) {
        price = _price;
        _decimals = decimals_;
    }

    function latestRoundData() external view returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) {
        return (1, price, block.timestamp, block.timestamp, 1);
    }

    function decimals() external view returns (uint8) {
        return _decimals;
    }

    function setPrice(int256 _price) external {
        price = _price;
    }
}
MockERC20.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) {
        _mint(msg.sender, 1000000 * 10 ** uint(decimals));
    }
}
NonPayable.sol 20 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

contract NonPayable {
    bool public isPayable;

    function setPayable(bool _isPayable) external {
        isPayable = _isPayable;
    }

    function forward(address _dest, bytes calldata _data) external payable {
        (bool success, bytes memory returnData) = _dest.call{ value: msg.value }(_data);
        require(success, string(returnData));
    }

    receive() external payable {
        require(isPayable);
    }
}
PositionControllerTester.sol 72 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/PositionController.sol";

contract PositionControllerTester is PositionController {
    IActivePool public activePool;
    IDefaultPool public defaultPool;
    IPriceFeed public priceFeed;

    function getNewICRFromPositionChange
    (
        uint _coll,
        uint _debt,
        uint _collChange,
        bool isCollIncrease,
        uint _debtChange,
        bool isDebtIncrease,
        uint _price,
        uint8 _collateralDecimals
    )
    external
    pure
    returns (uint)
    {
        return _getNewICRFromPositionChange(_coll, _debt, _collChange, isCollIncrease, _debtChange, isDebtIncrease, _price, _collateralDecimals);
    }

    function getNewTCRFromPositionChange
    (
        address asset,
        uint8 version,
        uint _collChange,
        bool isCollIncrease,
        uint _debtChange,
        bool isDebtIncrease,
        uint _price
    )
    external
    view
    returns (uint)
    {
        return _getNewTCRFromPositionChange(
            collateralController.getCollateralInstance(asset, version),
            _collChange, isCollIncrease, _debtChange, isDebtIncrease, _price
        );
    }

    function getUSDValue(uint _coll, uint _price) external pure returns (uint) {
        return _getUSDValue(_coll, _price);
    }

    function callInternalAdjustLoan
    (
        address _asset,
        uint8 version,
        uint _collAddition,
        address _borrower,
        uint _collWithdrawal,
        uint _debtChange,
        bool _isDebtIncrease,
        address _upperHint,
        address _lowerHint)
        external
    {
        _adjustPosition(AdjustPositionParams(_asset, version, _collAddition, _borrower, _collWithdrawal, _debtChange, _isDebtIncrease, _upperHint, _lowerHint, 0));
    }


    // Payable fallback function
    receive() external payable { }
}
PriceFeedTester.sol 57 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/collateral/oracle/TitanXPriceFeed.sol";

contract PriceFeedTester is TitanXPriceFeed {
    constructor(
        address uniFactoryAddress,
        address titanx,
        address weth,
        address _positionController,
        address _collateralController,
        address[] memory ethOracles
    ) TitanXPriceFeed(
        uniFactoryAddress,
        titanx,
        weth,
        _positionController,
        _collateralController,
        ethOracles
    ) {}

    function getOldestObservation(uint32 _secondsAgo) external view returns (uint32 secondsAgo) {
        address poolAddress = PoolAddress.computeAddress(
            UNI_FACTORY,
            PoolAddress.getPoolKey(TITANX, WETH, FEE_TIER)
        );

        return OracleLibrary.getOldestObservationSecondsAgo(poolAddress);
    }

    function simulateDeviationTest(
        uint spotUsd,
        uint shortUsd,
        uint longUsd,
        uint maxOracleDeltaPCT,
        bool considerSpot
    ) external view returns (bool, uint) {
        PriceData memory priceData = PriceData(spotUsd, shortUsd, longUsd);

        uint minPrice;
        uint maxPrice;

        if (considerSpot) {
            minPrice = StableMath._min(StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd), priceData.spotUsd);
            maxPrice = StableMath._max(StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd), priceData.spotUsd);
        } else {
            minPrice = StableMath._min(priceData.sTwapUsd, priceData.lTwapUsd);
            maxPrice = StableMath._max(priceData.sTwapUsd, priceData.lTwapUsd);
        }

        return (
            _priceDeviationInSafeRange(priceData, considerSpot, maxOracleDeltaPCT),
            _calcDiff(minPrice, maxPrice)
        );
    }
}
PriceFeedTestnet.sol 101 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../../interfaces/IPriceFeed.sol";

/*
* Placeholder for testnet and development. The price is simply set manually and saved in a state
* variable. The contract does not connect to a live Chainlink price feed.
*/
contract PriceFeedTestnet is IPriceFeed {
    uint256 private _price = 200 * 1e18;

    // Access control state
    bool public accessControlsEnabled;
    address public positionController;
    address public collateralController;
    address public positionManager;

    // ======== MODIFIERS ========

    modifier onlyPC {
        if (accessControlsEnabled) {
            require(msg.sender == positionController, "OnlyPC");
        }
        _;
    }

    modifier onlyPM {
        if (accessControlsEnabled) {
            require(msg.sender == positionManager, "OnlyPM");
        }
        _;
    }

    modifier onlyPCorCC {
        if (accessControlsEnabled) {
            require(msg.sender == positionController || msg.sender == collateralController, "OnlyPCorCC");
        }
        _;
    }

    constructor(
        address _uniFactoryAddress,
        address _titanX,
        address _weth,
        address _positionController,
        address _collateralController,
        address[] memory _priceAggregatorAddresses
    ) {
        positionController = _positionController;
        collateralController = _collateralController;
        accessControlsEnabled = false;
    }

    // ======== ACCESS CONTROL MANAGEMENT ========

    function enableAccessControls() external {
        accessControlsEnabled = true;
    }

    function disableAccessControls() external {
        accessControlsEnabled = false;
    }

    // ======== PRICE FEED FUNCTIONS ========

    function getPrice() external view returns (uint256) {
        return _price;
    }

    function setPositionManager(address pm) external {
        positionManager = pm;
    }

    function fetchPrice(uint utilizationPCT) external view returns (PriceDetails memory) {
        return PriceDetails(_price, _price, _price, _price, _price, _price, 0, OracleMode.AUTOMATED);
    }

    function fetchWeightedAveragePrice(bool testLiquidity, bool testDeviation) external override onlyPM returns (uint price) {
        return _price;
    }

    function fetchLowestPrice(bool testLiquidity, bool testDeviation) external override onlyPCorCC returns (uint price) {
        return _price;
    }

    function fetchLowestPriceWithFeeSuggestion(uint loadInc, uint originationOrRedemptionLoadPCT, bool testLiquidity, bool testDeviation) external override onlyPC returns (uint price, uint recommendedFee) {
        return (_price, 0);
    }

    function fetchHighestPriceWithFeeSuggestion(uint loadInc, uint originationOrRedemptionLoadPCT, bool testLiquidity, bool testDeviation) external override onlyPM returns (uint price, uint recommendedFee) {
        return (_price, 0);
    }

    function setPrice(uint256 price) external returns (bool) {
        _price = price;
        return true;
    }
}
SortedPositionsTester.sol 32 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../interfaces/ISortedPositions.sol";

contract SortedPositionsTester {
    ISortedPositions public sortedPositions;

    function setSortedPositions(address _sortedPositionsAddress) external {
        sortedPositions = ISortedPositions(_sortedPositionsAddress);
    }

    function insert(address _id, uint256 _NICR, address _prevId, address _nextId) external {
        sortedPositions.insert(_id, _NICR, _prevId, _nextId);
    }

    function remove(address _id) external {
        sortedPositions.remove(_id);
    }

    function reInsert(address _id, uint256 _newNICR, address _prevId, address _nextId) external {
        sortedPositions.reInsert(_id, _newNICR, _prevId, _nextId);
    }

    function getNominalICR(address) external pure returns (uint) {
        return 1;
    }

    function getCurrentICR(address, uint) external pure returns (uint) {
        return 1;
    }
}
StableMathTester.sol 21 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;

import "../../common/StableMath.sol";

contract StableMathTester {
    function callMax(uint _a, uint _b) external pure returns (uint) {
        return StableMath._max(_a, _b);
    }

    // Non-view wrapper for gas test
    function callDecPowTx(uint _base, uint _n) external pure returns (uint) {
        return StableMath._decPow(_base, _n);
    }

    // External wrapper
    function callDecPow(uint _base, uint _n) external pure returns (uint) {
        return StableMath._decPow(_base, _n);
    }
}
StableTokenCaller.sol 28 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../interfaces/IStable.sol";

contract StableTokenCaller {
    IStable public stable;

    function setStable(IStable _stable) external {
        stable = _stable;
    }

    function stableMint(address _account, uint _amount) external {
        stable.mint(_account, _amount);
    }

    function stableBurn(address _account, uint _amount) external {
        stable.burn(_account, _amount);
    }

    function stableSendToPool(address _sender,  address _poolAddress, uint256 _amount) external {
        stable.sendToPool(_sender, _poolAddress, _amount);
    }

    function stableReturnFromPool(address _poolAddress, address _receiver, uint256 _amount) external {
        stable.returnFromPool(_poolAddress, _receiver, _amount);
    }
}
StableTokenTester.sol 55 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.21;
import "../../stable/Stable.sol";

contract StableTokenTester is Stable {
    
    bytes32 private immutable _PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    
    constructor( 
        address _positionManagerAddress,
        address _backstopPoolAddress,
        address _positionControllerAddress
    ) Stable(_positionManagerAddress, _backstopPoolAddress, _positionControllerAddress) {}
    
    function unprotectedMint(address _account, uint256 _amount) external {
        // No check on caller here

        _mint(_account, _amount);
    }

    function unprotectedBurn(address _account, uint _amount) external {
        // No check on caller here
        _burn(_account, _amount);
    }

    function unprotectedSendToPool(address _sender, address _poolAddress, uint256 _amount) external {
        // No check on caller here
        _transfer(_sender, _poolAddress, _amount);
    }

    function unprotectedReturnFromPool(address _poolAddress, address _receiver, uint256 _amount ) external {
        // No check on caller here
        _transfer(_poolAddress, _receiver, _amount);
    }

    function callInternalApprove(address owner, address spender, uint256 amount) external returns (bool) {
        _approve(owner, spender, amount);
    }

    function getChainId() external view returns (uint256 chainID) {
        //return _chainID(); // it’s private
        assembly {
            chainID := chainid()
        }
    }

    function version() external view returns (string memory) {
        return "1";
    }

    function recoverAddress(bytes32 digest, uint8 v, bytes32 r, bytes32 s) external pure returns (address) {
        return ecrecover(digest, v, r, s);
    }
}
TestCollateral.sol 20 lines
pragma solidity ^0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestCollateral is ERC20 {
    uint8 private immutable _decimals;

    constructor(string memory _name, string memory _symbol, uint8 __decimals, uint _supply) ERC20(_name, _symbol) {
        _mint(msg.sender, _supply);
        _decimals = __decimals;
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    function mint(uint amount) external {
        _mint(msg.sender, amount);
    }
}
wETH.sol 59 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.21;

contract wETH {
    string public name = "Wrapped Ether";
    string public symbol = "WETH";
    uint8  public decimals = 18;

    event Approval(address indexed src, address indexed guy, uint wad);
    event Transfer(address indexed src, address indexed dst, uint wad);
    event Deposit(address indexed dst, uint wad);
    event Withdrawal(address indexed src, uint wad);

    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;

    receive() external payable {
        deposit();
    }

    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        payable(msg.sender).transfer(wad);
        emit Withdrawal(msg.sender, wad);
    }

    function totalSupply() public view returns (uint) {
        return address(this).balance;
    }

    function approve(address guy, uint wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        emit Approval(msg.sender, guy, wad);
        return true;
    }

    function transfer(address dst, uint wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }

    function transferFrom(address src, address dst, uint wad) public returns (bool) {
        require(balanceOf[src] >= wad);
        if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }
        balanceOf[src] -= wad;
        balanceOf[dst] += wad;
        emit Transfer(src, dst, wad);
        return true;
    }
}

Read Contract

MAX_ALLOWED_DEVIATION 0x7c9e639f → uint256
MIN_ALLOWED_DEVIATION 0x11449b61 → uint256
TARGET_DIGITS 0x1be5c92f → uint256
TIMEOUT 0xf56f48f2 → uint256
collateralController 0x69076788 → address
fetchPrice 0x1559f782 → tuple
guardian 0x452a9320 → address
lastGoodPrice 0x0490be83 → uint256
maxPriceDeviationFromPreviousRound 0x243a6e6c → uint256
oogGriefingProtectionEnabled 0x48c9d067 → bool
positionController 0x00551c06 → address
positionManager 0x791b98bc → address
priceAggregator 0x3078fff5 → address
status 0x200d2ed2 → uint8
viewEthPrice 0x2217455a → uint256

Write Contract 11 functions

These functions modify contract state and require a wallet transaction to execute.

fetchEthPrice 0xf2041b38
No parameters
returns: uint256
fetchHighestPriceWithFeeSuggestion 0xcf0b00c6
uint256 loadIncrease
uint256 originationOrRedemptionLoadPCT
bool testLiquidity
bool testDeviation
returns: uint256, uint256
fetchLowestPrice 0xd541d9e6
bool testLiquidity
bool testDeviation
returns: uint256
fetchLowestPriceWithFeeSuggestion 0x22cb9c6f
uint256 loadIncrease
uint256 originationOrRedemptionLoadPCT
bool testLiquidity
bool testDeviation
returns: uint256, uint256
fetchWeightedAveragePrice 0xb566aa1a
bool testLiquidity
bool testDeviation
returns: uint256
releaseGuard 0x2f60d071
No parameters
resetBreaker 0x931cc314
uint256 newPrice
setGriefingProtection 0xff29dd96
bool enabled
setMaxPriceDeviation 0x9ee4c057
uint256 newDeviation
setPositionManager 0x5760f2e3
address pm
transferWatch 0x34a93f25
address newGuardian

Recent Transactions

No transactions found for this address