creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "src/StakingManager.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { Ownable2Step } from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\nimport { Pausable } from \"@openzeppelin/contracts/utils/Pausable.sol\";\nimport { ReentrancyGuard } from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport { Staker } from \"./Staker.sol\";\n\nimport { IHoldingManager } from \"./interfaces/IHoldingManager.sol\";\nimport { IIonPool } from \"./interfaces/IIonPool.sol\";\nimport { IStakingManager } from \"./interfaces/IStakingManager.sol\";\nimport { IStaker } from \"./interfaces/IStaker.sol\";\n\n/**\n * @title StakingManager\n *\n * @notice Manages the distribution of rewards to early users of Jigsaw by facilitating staking of underlying assets.\n * @notice Staked assets are deposited into Ion's `Pool` Contract to generate yield and earn jPoints, redeemable for\n * governance $JIG tokens.\n * @notice For more information on Ion Protocol, visit https://ionprotocol.io.\n *\n * @dev This contract inherits functionalities from `Pausable`, `ReentrancyGuard`, and `Ownable2Step`.\n *\n * @author Hovooo (@hovooo)\n *\n * @custom:security-contact support@jigsaw.finance\n */\ncontract StakingManager is IStakingManager, Pausable, ReentrancyGuard, Ownable2Step {\n    using SafeERC20 for IERC20;\n\n    /**\n     * @notice Address of the Holding Manager contract.\n     * @dev The Holding Manager is responsible for creating and managing user Holdings.\n     */\n    IHoldingManager public immutable override holdingManager;\n\n    /**\n     * @notice Address of the underlying asset used for staking.\n     */\n    address public immutable override underlyingAsset;\n\n    /**\n     * @notice Address of the reward token distributed for staking.\n     */\n    address public immutable override rewardToken;\n\n    /**\n     * @notice Address of the Ion Pool contract.\n     */\n    address public immutable override ionPool;\n\n    /**\n     * @notice Address of the Staker contract used for jPoints distribution.\n     */\n    address public immutable override staker;\n\n    /**\n     * @notice Represents the expiration date for the staking lockup period.\n     * After this date, staked funds can be withdrawn.\n     * @dev If not withdrawn will continue to generate rewards in `underlyingAsset` and,\n     * if applicable, additional jPoints as long as staked.\n     * @return The expiration date for the staking lockup period, in Unix timestamp format.\n     */\n    uint256 public override lockupExpirationDate;\n\n    // --- Modifiers ---\n\n    /**\n     * @notice Modifier to check if the provided amount is valid.\n     * @param _amount to be checked for validity.\n     */\n    modifier validAmount(uint256 _amount) {\n        if (_amount == 0) revert InvalidAmount();\n        _;\n    }\n\n    /**\n     * @notice Modifier to check if the provided address is valid.\n     * @param _address to be checked for validity.\n     */\n    modifier validAddress(address _address) {\n        if (_address == address(0)) revert InvalidAddress();\n        _;\n    }\n\n    // --- Constructor ---\n\n    /**\n     * @notice Constructor function for initializing the StakerManager contract.\n     *\n     * @param _initialOwner Address of the initial owner.\n     * @param _holdingManager Address of the holding manager contract.\n     * @param _underlyingAsset Address of the underlying asset used for staking.\n     * @param _rewardToken Address of the reward token.\n     * @param _ionPool Address of the IonPool Contract.\n     * @param _rewardsDuration Duration of the rewards period.\n     */\n    constructor(\n        address _initialOwner,\n        address _holdingManager,\n        address _underlyingAsset,\n        address _rewardToken,\n        address _ionPool,\n        uint256 _rewardsDuration\n    )\n        Ownable(_initialOwner)\n        validAddress(_holdingManager)\n        validAddress(_underlyingAsset)\n        validAddress(_rewardToken)\n        validAddress(_ionPool)\n        validAmount(_rewardsDuration)\n    {\n        holdingManager = IHoldingManager(_holdingManager);\n        underlyingAsset = _underlyingAsset;\n        rewardToken = _rewardToken;\n        ionPool = _ionPool;\n        staker = address(\n            new Staker({\n                _initialOwner: _initialOwner,\n                _tokenIn: _underlyingAsset,\n                _rewardToken: _rewardToken,\n                _stakingManager: address(this),\n                _rewardsDuration: _rewardsDuration\n            })\n        );\n        lockupExpirationDate = block.timestamp + _rewardsDuration;\n    }\n\n    /**\n     * @notice Stakes a specified amount of assets for the msg.sender.\n     * @dev Initiates the staking operation by transferring the specified `_amount` from the user's wallet to the\n     * contract, while simultaneously recording this deposit within the Jigsaw `Staker` Contract.\n     *\n     * @notice Requirements:\n     * - The caller must have sufficient assets to stake.\n     * - The Ion `Pool` Contract's supply cap should not exceed its limit after the user's stake operation.\n     * - Prior approval is required for this contract to transfer assets on behalf of the user.\n     *\n     * @notice Effects:\n     * - If the user does not have an existing holding, a new holding is created for the user.\n     * - Supplies the specified amount of underlying asset to the Ion's `Pool` Contract to earn interest.\n     * - Tracks the deposit in the `Staker` Contract to earn jPoints for staking.\n     *\n     * @notice Emits:\n     * - `Staked` event indicating the staking action.\n     *\n     * @param _amount of assets to stake.\n     */\n    function stake(uint256 _amount) external override nonReentrant whenNotPaused validAmount(_amount) {\n        // Create a holding for msg.sender if there is no holding associated with their address yet.\n        address holding = holdingManager.getUserHolding(msg.sender);\n        if (holding == address(0)) holding = holdingManager.createHolding(msg.sender);\n\n        // Emit an event indicating the staking action.\n        emit Staked(msg.sender, _amount);\n\n        // Transfer assets from the user's wallet to this contract.\n        IERC20(underlyingAsset).safeTransferFrom({ from: msg.sender, to: address(this), value: _amount });\n        // Approve Ion Pool contract to spend the transferred assets.\n        IERC20(underlyingAsset).safeIncreaseAllowance({ spender: ionPool, value: _amount });\n\n        // Supply to the Ion Pool to earn interest on underlying asset.\n        IIonPool(ionPool).supply({ user: holding, amount: _amount, proof: new bytes32[](0) });\n        // Track deposit in Staker to earn jPoints for staking.\n        IStaker(staker).deposit({ _user: holding, _amount: _amount });\n    }\n\n    /**\n     * @notice Performs unstake operation.\n     * @dev Initiates the withdrawal of staked assets by transferring all the deposited assets plus generated yield from\n     * the Ion's `Pool` Contract and earned jPoint rewards from `Staker` Contract to the designated recipient `_to`.\n     *\n     * @notice Requirements:\n     * - The `lockupExpirationDate` should have already expired.\n     * - The caller must possess sufficient staked assets to fulfill the withdrawal.\n     * - The `_to` address must be a valid Ethereum address.\n     *\n     * @notice Effects:\n     * - Unstakes deposited and accrued underlying assets from Ion's `Pool` Contract.\n     * - Withdraws jPoint rewards from `Staker` Contract.\n     * - Withdraws Ion rewards from `Holding` through `HoldingManager` Contract.\n     *\n     *\n     * @notice Emits:\n     * - `Unstaked` event indicating the unstaking action.\n     *\n     * @param _to address to receive the unstaked assets.\n     */\n    function unstake(address _to) external override nonReentrant whenNotPaused validAddress(_to) {\n        // Check if the lockup expiration date has passed.\n        if (lockupExpirationDate > block.timestamp) revert PreLockupPeriodUnstaking();\n        // Get the holding address of the caller.\n        address holding = holdingManager.getUserHolding(msg.sender);\n\n        // If the caller has no balance in the Ion Pool, revert with `NothingToWithdrawFromIon` error.\n        uint256 ionPoolBalance = IIonPool(ionPool).balanceOf(holding);\n        if (ionPoolBalance == 0) revert NothingToWithdrawFromIon(msg.sender);\n\n        // Emit an event indicating the unstaking action.\n        emit Unstaked(msg.sender, IStaker(staker).balanceOf(holding));\n\n        // Unstake assets and withdraw rewards and transfer them to the specified address.\n        holdingManager.unstake({ _holding: holding, _pool: ionPool, _to: _to, _amount: ionPoolBalance });\n        IStaker(staker).exit({ _user: holding, _to: _to });\n    }\n\n    // --- Administration ---\n\n    /**\n     * @notice Triggers stopped state.\n     */\n    function pause() external override onlyOwner whenNotPaused {\n        _pause();\n    }\n\n    /**\n     * @notice Returns to normal state.\n     */\n    function unpause() external override onlyOwner whenPaused {\n        _unpause();\n    }\n\n    /**\n     * @notice Renounce ownership override to prevent accidental loss of contract ownership.\n     * @dev This function ensures that the contract's ownership cannot be lost unintentionally.\n     */\n    function renounceOwnership() public pure override(IStakingManager, Ownable) {\n        revert RenouncingOwnershipProhibited();\n    }\n\n    /**\n     * @notice Allows the default admin role to set a new lockup expiration date.\n     *\n     * @notice Requirements:\n     * - Caller must be `Owner`.\n     * - `_newDate` should be different from `lockupExpirationDate`.\n     *\n     * @notice Effects:\n     * - Sets `lockupExpirationDate` to `_newDate`.\n     *\n     *  @notice Emits:\n     * - `LockupExpirationDateUpdated` event indicating that lockup expiration date has been updated.\n     *\n     * @param _newDate The new lockup expiration date to be set.\n     */\n    function setLockupExpirationDate(uint256 _newDate) external onlyOwner {\n        // Sanity check that the `_newDate` is different from `lockupExpirationDate`.\n        if (lockupExpirationDate == _newDate) revert SameValue();\n        // Emit event indicating that the lockup expiration date has been updated.\n        emit LockupExpirationDateUpdated(lockupExpirationDate, _newDate);\n        // Update the state variable.\n        lockupExpirationDate = _newDate;\n    }\n\n    // --- Getters ---\n\n    /**\n     * @notice Get the address of the holding associated with the user.\n     * @param _user The address of the user.\n     * @return the holding address.\n     */\n    function getUserHolding(address _user) external view override returns (address) {\n        return holdingManager.getUserHolding(_user);\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n    /**\n     * @dev Emitted when `value` tokens are moved from one account (`from`) to\n     * another (`to`).\n     *\n     * Note that `value` may be zero.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 value);\n\n    /**\n     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n     * a call to {approve}. `value` is the new allowance.\n     */\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n\n    /**\n     * @dev Returns the value of tokens in existence.\n     */\n    function totalSupply() external view returns (uint256);\n\n    /**\n     * @dev Returns the value of tokens owned by `account`.\n     */\n    function balanceOf(address account) external view returns (uint256);\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transfer(address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Returns the remaining number of tokens that `spender` will be\n     * allowed to spend on behalf of `owner` through {transferFrom}. This is\n     * zero by default.\n     *\n     * This value changes when {approve} or {transferFrom} are called.\n     */\n    function allowance(address owner, address spender) external view returns (uint256);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * IMPORTANT: Beware that changing an allowance with this method brings the risk\n     * that someone may use both the old and the new allowance by unfortunate\n     * transaction ordering. One possible solution to mitigate this race\n     * condition is to first reduce the spender's allowance to 0 and set the\n     * desired value afterwards:\n     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address spender, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the\n     * allowance mechanism. `value` is then deducted from the caller's\n     * allowance.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {Ownable} from \"./Ownable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n    address private _pendingOwner;\n\n    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Returns the address of the pending owner.\n     */\n    function pendingOwner() public view virtual returns (address) {\n        return _pendingOwner;\n    }\n\n    /**\n     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual override onlyOwner {\n        _pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner(), newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual override {\n        delete _pendingOwner;\n        super._transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev The new owner accepts the ownership transfer.\n     */\n    function acceptOwnership() public virtual {\n        address sender = _msgSender();\n        if (pendingOwner() != sender) {\n            revert OwnableUnauthorizedAccount(sender);\n        }\n        _transferOwnership(sender);\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/utils/Pausable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n    bool private _paused;\n\n    /**\n     * @dev Emitted when the pause is triggered by `account`.\n     */\n    event Paused(address account);\n\n    /**\n     * @dev Emitted when the pause is lifted by `account`.\n     */\n    event Unpaused(address account);\n\n    /**\n     * @dev The operation failed because the contract is paused.\n     */\n    error EnforcedPause();\n\n    /**\n     * @dev The operation failed because the contract is not paused.\n     */\n    error ExpectedPause();\n\n    /**\n     * @dev Initializes the contract in unpaused state.\n     */\n    constructor() {\n        _paused = false;\n    }\n\n    /**\n     * @dev Modifier to make a function callable only when the contract is not paused.\n     *\n     * Requirements:\n     *\n     * - The contract must not be paused.\n     */\n    modifier whenNotPaused() {\n        _requireNotPaused();\n        _;\n    }\n\n    /**\n     * @dev Modifier to make a function callable only when the contract is paused.\n     *\n     * Requirements:\n     *\n     * - The contract must be paused.\n     */\n    modifier whenPaused() {\n        _requirePaused();\n        _;\n    }\n\n    /**\n     * @dev Returns true if the contract is paused, and false otherwise.\n     */\n    function paused() public view virtual returns (bool) {\n        return _paused;\n    }\n\n    /**\n     * @dev Throws if the contract is paused.\n     */\n    function _requireNotPaused() internal view virtual {\n        if (paused()) {\n            revert EnforcedPause();\n        }\n    }\n\n    /**\n     * @dev Throws if the contract is not paused.\n     */\n    function _requirePaused() internal view virtual {\n        if (!paused()) {\n            revert ExpectedPause();\n        }\n    }\n\n    /**\n     * @dev Triggers stopped state.\n     *\n     * Requirements:\n     *\n     * - The contract must not be paused.\n     */\n    function _pause() internal virtual whenNotPaused {\n        _paused = true;\n        emit Paused(_msgSender());\n    }\n\n    /**\n     * @dev Returns to normal state.\n     *\n     * Requirements:\n     *\n     * - The contract must be paused.\n     */\n    function _unpause() internal virtual whenPaused {\n        _paused = false;\n        emit Unpaused(_msgSender());\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n    // Booleans are more expensive than uint256 or any type that takes up a full\n    // word because each write operation emits an extra SLOAD to first read the\n    // slot's contents, replace the bits taken up by the boolean, and then write\n    // back. This is the compiler's defense against contract upgrades and\n    // pointer aliasing, and it cannot be disabled.\n\n    // The values being non-zero value makes deployment a bit more expensive,\n    // but in exchange the refund on every call to nonReentrant will be lower in\n    // amount. Since refunds are capped to a percentage of the total\n    // transaction's gas, it is best to keep them low in cases like this one, to\n    // increase the likelihood of the full refund coming into effect.\n    uint256 private constant NOT_ENTERED = 1;\n    uint256 private constant ENTERED = 2;\n\n    uint256 private _status;\n\n    /**\n     * @dev Unauthorized reentrant call.\n     */\n    error ReentrancyGuardReentrantCall();\n\n    constructor() {\n        _status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Prevents a contract from calling itself, directly or indirectly.\n     * Calling a `nonReentrant` function from another `nonReentrant`\n     * function is not supported. It is possible to prevent this from happening\n     * by making the `nonReentrant` function external, and making it call a\n     * `private` function that does the actual work.\n     */\n    modifier nonReentrant() {\n        _nonReentrantBefore();\n        _;\n        _nonReentrantAfter();\n    }\n\n    function _nonReentrantBefore() private {\n        // On the first call to nonReentrant, _status will be NOT_ENTERED\n        if (_status == ENTERED) {\n            revert ReentrancyGuardReentrantCall();\n        }\n\n        // Any calls to nonReentrant after this point will fail\n        _status = ENTERED;\n    }\n\n    function _nonReentrantAfter() private {\n        // By storing the original value once again, a refund is triggered (see\n        // https://eips.ethereum.org/EIPS/eip-2200)\n        _status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n     * `nonReentrant` function in the call stack.\n     */\n    function _reentrancyGuardEntered() internal view returns (bool) {\n        return _status == ENTERED;\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC20Permit} from \"../extensions/IERC20Permit.sol\";\nimport {Address} from \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n    using Address for address;\n\n    /**\n     * @dev An operation with an ERC20 token failed.\n     */\n    error SafeERC20FailedOperation(address token);\n\n    /**\n     * @dev Indicates a failed `decreaseAllowance` request.\n     */\n    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n    /**\n     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful.\n     */\n    function safeTransfer(IERC20 token, address to, uint256 value) internal {\n        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n    }\n\n    /**\n     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n     */\n    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n    }\n\n    /**\n     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful.\n     */\n    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n        uint256 oldAllowance = token.allowance(address(this), spender);\n        forceApprove(token, spender, oldAllowance + value);\n    }\n\n    /**\n     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n     * value, non-reverting calls are assumed to be successful.\n     */\n    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n        unchecked {\n            uint256 currentAllowance = token.allowance(address(this), spender);\n            if (currentAllowance < requestedDecrease) {\n                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n            }\n            forceApprove(token, spender, currentAllowance - requestedDecrease);\n        }\n    }\n\n    /**\n     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n     * to be set to zero before setting it to a non-zero value, such as USDT.\n     */\n    function forceApprove(IERC20 token, address spender, uint256 value) internal {\n        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n        if (!_callOptionalReturnBool(token, approvalCall)) {\n            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n            _callOptionalReturn(token, approvalCall);\n        }\n    }\n\n    /**\n     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n     * on the return value: the return value is optional (but if data is returned, it must not be false).\n     * @param token The token targeted by the call.\n     * @param data The call data (encoded using abi.encode or one of its variants).\n     */\n    function _callOptionalReturn(IERC20 token, bytes memory data) private {\n        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n        // the target address contains contract code and also asserts for success in the low-level call.\n\n        bytes memory returndata = address(token).functionCall(data);\n        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {\n            revert SafeERC20FailedOperation(address(token));\n        }\n    }\n\n    /**\n     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n     * on the return value: the return value is optional (but if data is returned, it must not be false).\n     * @param token The token targeted by the call.\n     * @param data The call data (encoded using abi.encode or one of its variants).\n     *\n     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\n     */\n    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\n        // and not revert is the subcall reverts.\n\n        (bool success, bytes memory returndata) = address(token).call(data);\n        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;\n    }\n}\n"
      },
      "src/Staker.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport { Ownable2Step } from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\nimport { Ownable } from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport { Pausable } from \"@openzeppelin/contracts/utils/Pausable.sol\";\nimport { ReentrancyGuard } from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport { SafeERC20 } from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nimport { IStaker } from \"./interfaces/IStaker.sol\";\n\n/**\n * @title Staker\n * @notice Staker is a contract responsible for distributing rewards in the form of jPoints to early users\n * of the Jigsaw protocol.\n * @notice This contract accepts wstETH tokens as `tokenIn` and distributes rewards in jPoints accordingly.\n * @notice It is not intended for direct use; interaction should be done through the `stakingManager` contract.\n *\n * @dev This contract inherits functionalities from `Ownable2Step`, `Pausable`, and `ReentrancyGuard`.\n *\n * @author Hovooo (@hovooo)\n */\ncontract Staker is IStaker, Ownable2Step, Pausable, ReentrancyGuard {\n    using SafeERC20 for IERC20;\n\n    /**\n     * @notice Address of the staking token.\n     */\n    address public immutable override tokenIn;\n\n    /**\n     * @notice Address of the reward token.\n     */\n    address public immutable override rewardToken;\n\n    /**\n     * @notice Address of the staking manager.\n     */\n    address public immutable override stakingManager;\n\n    /**\n     * @notice Timestamp indicating when the current reward distribution ends.\n     */\n    uint256 public override periodFinish = 0;\n\n    /**\n     * @notice Rate of rewards per second.\n     */\n    uint256 public override rewardRate = 0;\n\n    /**\n     * @notice Duration of current reward period.\n     */\n    uint256 public override rewardsDuration;\n\n    /**\n     * @notice Timestamp of the last update time.\n     */\n    uint256 public override lastUpdateTime;\n\n    /**\n     * @notice Stored rewards per token.\n     */\n    uint256 public override rewardPerTokenStored;\n\n    /**\n     * @notice Mapping of user addresses to the amount of rewards already paid to them.\n     */\n    mapping(address => uint256) public override userRewardPerTokenPaid;\n\n    /**\n     * @notice Mapping of user addresses to their accrued rewards.\n     */\n    mapping(address => uint256) public override rewards;\n\n    /**\n     * @notice Total supply limit of the staking token.\n     */\n    uint256 public constant totalSupplyLimit = 1e34;\n\n    uint256 private _totalSupply;\n    mapping(address => uint256) private _balances;\n\n    // --- Modifiers ---\n\n    /**\n     * @notice Modifier to update the reward for a specified account.\n     * @param account The account for which the reward needs to be updated.\n     */\n    modifier updateReward(address account) {\n        rewardPerTokenStored = rewardPerToken();\n        lastUpdateTime = lastTimeRewardApplicable();\n        if (account != address(0)) {\n            rewards[account] = earned(account);\n            userRewardPerTokenPaid[account] = rewardPerTokenStored;\n        }\n        _;\n    }\n\n    /**\n     * @notice Modifier to check if the provided address is valid.\n     * @param _address to be checked for validity.\n     */\n    modifier validAddress(address _address) {\n        if (_address == address(0)) revert InvalidAddress();\n        _;\n    }\n\n    /**\n     * @notice Modifier to check if the provided amount is valid.\n     * @param _amount to be checked for validity.\n     */\n    modifier validAmount(uint256 _amount) {\n        if (_amount == 0) revert InvalidAmount();\n        _;\n    }\n\n    /**\n     * @notice Modifier to restrict a function to be called only by the staking manager.\n     * @notice Reverts the transaction if the caller is not the staking manager.\n     */\n    modifier onlyStakingManager() {\n        if (msg.sender != stakingManager) revert UnauthorizedCaller();\n        _;\n    }\n\n    // --- Constructor ---\n\n    /**\n     * @notice Constructor function for initializing the Staker contract.\n     *\n     * @param _initialOwner The initial owner of the contract\n     * @param _tokenIn The address of the token to be staked\n     * @param _rewardToken The address of the reward token\n     * @param _stakingManager The address of the staking manager contract\n     * @param _rewardsDuration The duration of the rewards period, in seconds\n     */\n    constructor(\n        address _initialOwner,\n        address _tokenIn,\n        address _rewardToken,\n        address _stakingManager,\n        uint256 _rewardsDuration\n    )\n        Ownable(_initialOwner)\n        validAddress(_tokenIn)\n        validAddress(_rewardToken)\n        validAddress(_stakingManager)\n        validAmount(_rewardsDuration)\n    {\n        tokenIn = _tokenIn;\n        rewardToken = _rewardToken;\n        stakingManager = _stakingManager;\n        rewardsDuration = _rewardsDuration;\n        periodFinish = block.timestamp + rewardsDuration;\n    }\n\n    // -- Staker's operations  --\n\n    /**\n     * @notice Performs a deposit operation for `_user`.\n     * @dev Updates participants' rewards.\n     *\n     * @param _user to deposit for.\n     * @param _amount to deposit.\n     */\n    function deposit(\n        address _user,\n        uint256 _amount\n    )\n        external\n        override\n        onlyStakingManager\n        whenNotPaused\n        nonReentrant\n        updateReward(_user)\n        validAmount(_amount)\n    {\n        uint256 rewardBalance = IERC20(rewardToken).balanceOf(address(this));\n        if (rewardBalance == 0) revert NoRewardsToDistribute();\n\n        // Ensure that deposit operation will never surpass supply limit\n        if (_totalSupply + _amount > totalSupplyLimit) revert DepositSurpassesSupplyLimit(_amount, totalSupplyLimit);\n        _totalSupply += _amount;\n\n        _balances[_user] += _amount;\n        emit Staked(_user, _amount);\n    }\n\n    /**\n     * @notice Withdraws specified `_amount` and claims rewards for the `_user`.\n     * @dev This function enables the caller to exit the investment and claim their rewards.\n     *\n     *  @param _user to withdraw and claim for.\n     *  @param _to address to which funds will be sent.\n     */\n    function exit(address _user, address _to) external override onlyStakingManager {\n        withdraw(_user, _balances[_user]);\n\n        if (rewards[_user] > 0) {\n            claimRewards(_user, _to);\n        }\n    }\n\n    /**\n     * @notice Withdraws investment from staking.\n     * @dev Updates participants' rewards.\n     *\n     * @param _user to withdraw for.\n     * @param _amount to withdraw.\n     */\n    function withdraw(\n        address _user,\n        uint256 _amount\n    )\n        internal\n        whenNotPaused\n        nonReentrant\n        updateReward(_user)\n        validAmount(_amount)\n    {\n        _totalSupply -= _amount;\n        _balances[_user] = _balances[_user] - _amount;\n        emit Withdrawn(_user, _amount);\n    }\n\n    /**\n     * @notice Claims the rewards for the caller.\n     * @dev This function allows the caller to claim their earned rewards.\n     *\n     *  @param _user to claim rewards for.\n     *  @param _to address to which rewards will be sent.\n     */\n    function claimRewards(address _user, address _to) internal nonReentrant updateReward(_user) {\n        uint256 reward = rewards[_user];\n        if (reward == 0) revert NothingToClaim();\n\n        rewards[_user] = 0;\n        emit RewardPaid(_user, reward);\n        IERC20(rewardToken).safeTransfer(_to, reward);\n    }\n\n    // -- Administration --\n\n    /**\n     * @notice Sets the duration of each reward period.\n     * @param _rewardsDuration The new rewards duration.\n     */\n    function setRewardsDuration(uint256 _rewardsDuration) external override onlyOwner {\n        if (block.timestamp <= periodFinish) revert PreviousPeriodNotFinished(block.timestamp, periodFinish);\n        emit RewardsDurationUpdated(rewardsDuration, _rewardsDuration);\n        rewardsDuration = _rewardsDuration;\n    }\n\n    /**\n     * @notice Adds more rewards to the contract.\n     *\n     * @dev Prior approval is required for this contract to transfer rewards from `_from` address.\n     *\n     * @param _from address to transfer rewards from.\n     * @param _amount The amount of new rewards.\n     */\n    function addRewards(\n        address _from,\n        uint256 _amount\n    )\n        external\n        override\n        onlyOwner\n        validAmount(_amount)\n        updateReward(address(0))\n    {\n        // Transfer assets from the user's wallet to this contract.\n        IERC20(rewardToken).safeTransferFrom({ from: _from, to: address(this), value: _amount });\n\n        uint256 duration = rewardsDuration;\n        if (duration == 0) revert ZeroRewardsDuration();\n        if (block.timestamp >= periodFinish) {\n            rewardRate = _amount / duration;\n        } else {\n            uint256 remaining = periodFinish - block.timestamp;\n            uint256 leftover = remaining * rewardRate;\n            rewardRate = (_amount + leftover) / duration;\n        }\n\n        if (rewardRate == 0) revert RewardAmountTooSmall();\n\n        uint256 balance = IERC20(rewardToken).balanceOf(address(this));\n        if (rewardRate > (balance / duration)) revert RewardRateTooBig();\n\n        lastUpdateTime = block.timestamp;\n        periodFinish = block.timestamp + duration;\n        emit RewardAdded(_amount);\n    }\n\n    /**\n     * @notice Triggers stopped state.\n     */\n    function pause() external override onlyOwner whenNotPaused {\n        _pause();\n    }\n\n    /**\n     * @notice Returns to normal state.\n     */\n    function unpause() external override onlyOwner whenPaused {\n        _unpause();\n    }\n\n    /**\n     * @notice Renounce ownership override to prevent accidental loss of contract ownership.\n     * @dev This function ensures that the contract's ownership cannot be lost unintentionally.\n     */\n    function renounceOwnership() public pure override {\n        revert RenouncingOwnershipProhibited();\n    }\n\n    // -- Getters --\n\n    /**\n     * @notice Returns the total supply of the staking token.\n     */\n    function totalSupply() external view override returns (uint256) {\n        return _totalSupply;\n    }\n\n    /**\n     * @notice Returns the total invested amount for an account.\n     * @param _account The participant's address.\n     */\n    function balanceOf(address _account) external view override returns (uint256) {\n        return _balances[_account];\n    }\n\n    /**\n     * @notice Returns the last time rewards were applicable.\n     */\n    function lastTimeRewardApplicable() public view override returns (uint256) {\n        return block.timestamp < periodFinish ? block.timestamp : periodFinish;\n    }\n\n    /**\n     * @notice Returns rewards per token.\n     */\n    function rewardPerToken() public view override returns (uint256) {\n        if (_totalSupply == 0) {\n            return rewardPerTokenStored;\n        }\n\n        return\n            rewardPerTokenStored + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18) / _totalSupply);\n    }\n\n    /**\n     * @notice Returns accrued rewards for an account.\n     * @param _account The participant's address.\n     */\n    function earned(address _account) public view override returns (uint256) {\n        return\n            ((_balances[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18) + rewards[_account];\n    }\n\n    /**\n     * @notice Returns the reward amount for a specific time range.\n     */\n    function getRewardForDuration() external view override returns (uint256) {\n        return rewardRate * rewardsDuration;\n    }\n}\n"
      },
      "src/interfaces/IHoldingManager.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\n/**\n * @title IHoldingManager interface\n * @dev Interface for the HoldingManager contract.\n */\ninterface IHoldingManager {\n    // --- Errors ---\n\n    /**\n     * @notice The operation failed because provided address is invalid.\n     */\n    error InvalidAddress();\n\n    /**\n     * @notice The operation failed because the same address was provided for an update.\n     */\n    error SameAddress();\n\n    /**\n     * @notice The operation failed because renouncing default admin role is prohibited.\n     */\n    error RenouncingDefaultAdminRoleProhibited();\n\n    /**\n     * @notice The operation failed because user attempts an action that requires a holding contract associated with\n     * their\n     * address, but no holding contract is found.\n     * @param user The address of the user who hasn't holding contract.\n     */\n    error MissingHoldingContractForUser(address user);\n\n    /**\n     * @notice The operation failed because the generic caller attempts to invoke a contract via a holding contract,\n     * but the allowance for the invocation is not permitted.\n     * @param caller The address of the generic caller attempting the invocation.\n     */\n    error InvocationNotAllowed(address caller);\n\n    /**\n     * @notice The operation failed because the generic call failed.\n     * @param data returned by the failed call.\n     */\n    error InvocationFailed(bytes data);\n\n    // --- Events ---\n    /**\n     * @notice Emitted when a holding is created for a participant.\n     * @param user address for whom the holding was created.\n     * @param holdingAddress address of the newly created holding.\n     */\n    event HoldingCreated(address indexed user, address indexed holdingAddress);\n\n    /**\n     * @notice emitted when the holding implementation reference is updated.\n     *\n     * @param oldReference address of the previous implementation reference.\n     * @param newReference address of the new implementation reference.\n     */\n    event HoldingImplementationReferenceUpdated(address indexed oldReference, address indexed newReference);\n\n    /**\n     * @notice emitted when the allowance for invoking contracts via a holding contract is set.\n     *\n     * @param holding The address of the holding contract.\n     * @param genericCaller The address of the generic caller.\n     * @param callableContract The address of the contract that can be invoked.\n     * @param oldAllowance of invocations allowed for the specified contract by the generic caller\n     * via the holding contract.\n     * @param newAllowance of invocations allowed for the specified contract by the generic caller\n     * via the holding contract.\n     */\n    event InvocationAllowanceSet(\n        address indexed holding,\n        address indexed genericCaller,\n        address indexed callableContract,\n        uint256 oldAllowance,\n        uint256 newAllowance\n    );\n\n    /**\n     * Declaration of the Staking Manager role - privileged actor, allowed to call unstake function on Holdings.\n     */\n    function STAKING_MANAGER_ROLE() external view returns (bytes32);\n\n    /**\n     * Declaration of the Generic Caller role - privileged actor, allowed to perform low level calls on Holdings.\n     */\n    function GENERIC_CALLER_ROLE() external view returns (bytes32);\n\n    /**\n     * @notice Returns the address of the holding implementation reference.\n     */\n    function holdingImplementationReference() external view returns (address);\n\n    /**\n     * @notice Sets the allowance for a generic caller to invoke specified contracts on behalf of the user\n     * through their holding contract.\n     *\n     * @notice Requirements:\n     * - `_genericCaller` must be a valid address.\n     * - `_callableContract` must be a valid address.\n     * - The caller must have a holding contract associated with their address.\n     * - The `_genericCaller` must have the `GENERIC_CALLER_ROLE`.\n     *\n     * @notice Effects:\n     * - Updates `_invocationsAllowance` mapping.\n     *\n     * @notice Emits:\n     * - Emits an `InvocationAllowanceSet` event upon successful execution.\n     *\n     * @param _genericCaller The address of the generic caller.\n     * @param _callableContract The address of the contract to be invoked.\n     * @param _invocationsAllowance The number of invocations allowed for the specified contract by the generic caller\n     * via the holding contract.\n     */\n    function setInvocationAllowance(\n        address _genericCaller,\n        address _callableContract,\n        uint256 _invocationsAllowance\n    )\n        external;\n\n    /**\n     * @notice Creates a new holding instance for the specified `user`.\n     * @dev Clones a new holding contract instance using the reference implementation and associates it with the\n     * `user`'s address and initializes the holding contract.\n     * @dev Emits an event to signify the creation of the holding contract.\n     *\n     * @param _user The address of the user.\n     *\n     * @return holding The address of the newly created holding contract.\n     */\n    function createHolding(address _user) external returns (address holding);\n\n    /**\n     * @notice Unstake funds from a the specified Ion Protocol's `_pool` contract for `_holding`.\n     *\n     * @param _holding address to unstake for.\n     * @param _pool address of Ion's pool.\n     * @param _to The address where unstaked tokens will be sent.\n     * @param _amount The amount of tokens to unstake.\n     */\n    function unstake(address _holding, address _pool, address _to, uint256 _amount) external;\n\n    /**\n     * @notice Allows a generic caller to invoke a function on a contract via a holding contract.\n     * This function performs a generic call on the specified contract address using the provided call data.\n     *\n     * @notice Requirements:\n     * - `_holding` must be a valid address representing the holding contract.\n     * - `_contract` must be a valid address representing the target contract.\n     * - The caller must have the `GENERIC_CALLER_ROLE`.\n     * - The allowance for the caller on the specified `_contract` via `_holding` must be greater than 0.\n     *\n     *  @notice Effects:\n     * - calls `genericCall` on the specified contract address using the provided call data.\n     *\n     * @param _holding The address of the holding contract where the call is invoked.\n     * @param _contract The external contract being called by the holding contract.\n     * @param _value The amount of Ether to transfer in the call.\n     * @param _call The call data.\n     *\n     * @return success Indicates whether the call was successful or not.\n     * @return result Data obtained from the external call.\n     */\n    function invokeHolding(\n        address _holding,\n        address _contract,\n        uint256 _value,\n        bytes calldata _call\n    )\n        external\n        returns (bool success, bytes memory result);\n\n    /**\n     * @notice Allows the Default Admin to set a new address for `holdingImplementationReference` to be cloned from.\n     *\n     * @notice Requirements:\n     * - Caller must have the `DEFAULT_ADMIN_ROLE`.\n     * - `_newReference` should be valid address.\n     *\n     * @notice Emits:\n     * - `HoldingImplementationReferenceUpdated` event indicating that holding implementation reference\n     * has been updated.\n     *\n     * @param _newReference The address of the new implementation reference.\n     */\n    function setHoldingImplementationReference(address _newReference) external;\n\n    /**\n     * @notice Prevents the renouncement of the `DEFAULT_ADMIN_ROLE` by overriding `beginDefaultAdminTransfer` function\n     * of the `AccessControlDefaultAdminRules` extension of the AccessControl.\n     * @param newAdmin address of the new admin.\n     */\n    function beginDefaultAdminTransfer(address newAdmin) external;\n\n    /**\n     * @notice Get the address of the holding associated with the user.\n     * @param _user The address of the user.\n     * @return the holding address.\n     */\n    function getUserHolding(address _user) external view returns (address);\n\n    /**\n     * @notice Get the allowance for a generic caller to invoke contracts via a holding contract.\n     *\n     * @param _user The address of the user.\n     * @param _genericCaller The address of the generic caller.\n     * @param _callableContract The address of the contract to be invoked.\n     * @return The number of invocations allowed for the specified contract by the generic caller\n     * via the holding contract.\n     */\n    function getInvocationAllowance(\n        address _user,\n        address _genericCaller,\n        address _callableContract\n    )\n        external\n        view\n        returns (uint256);\n}\n"
      },
      "src/interfaces/IIonPool.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ninterface IIonPool {\n    /**\n     * @dev Allows lenders to redeem their interest-bearing position for the\n     * underlying asset. It is possible that dust amounts more of the position\n     * are burned than the underlying received due to rounding.\n     * @param receiverOfUnderlying the address to which the redeemed underlying\n     * asset should be sent to.\n     * @param amount of underlying to redeem.\n     */\n    function withdraw(address receiverOfUnderlying, uint256 amount) external;\n\n    /**\n     * @dev Allows lenders to deposit their underlying asset into the pool and\n     * earn interest on it.\n     * @param user the address to receive credit for the position.\n     * @param amount of underlying asset to use to create the position.\n     * @param proof merkle proof that the user is whitelisted.\n     */\n    function supply(address user, uint256 amount, bytes32[] calldata proof) external;\n\n    /**\n     * @dev Current token balance\n     * @param user to get balance of\n     */\n    function balanceOf(address user) external view returns (uint256);\n}\n"
      },
      "src/interfaces/IStakingManager.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport { IHoldingManager } from \"./IHoldingManager.sol\";\n\n/**\n * @title IStakingManager interface\n * @notice Interface for the Staking Manager contract of the Jigsaw Protocol\n *\n */\ninterface IStakingManager {\n    // --- Errors ---\n    /**\n     * @notice The operation failed because renouncing ownership is prohibited.\n     */\n    error RenouncingOwnershipProhibited();\n\n    /**\n     * @notice The operation failed because amount is invalid.\n     */\n    error InvalidAmount();\n\n    /**\n     * @notice The operation failed because the same value was provided for an update.\n     */\n    error SameValue();\n\n    /**\n     * @notice The operation failed because provided address is invalid.\n     */\n    error InvalidAddress();\n\n    /**\n     * @notice The operation failed because unstaking is not possible before lockup period ends.\n     */\n    error PreLockupPeriodUnstaking();\n\n    /**\n     * @notice The operation failed because caller's holding balance in the Ion Pool is zero.\n     * @param caller address whose holding balance is zero.\n     */\n    error NothingToWithdrawFromIon(address caller);\n\n    // --- Events ---\n    /**\n     * @notice Emitted when a participant stakes tokens.\n     * @param user address of the participant who staked.\n     * @param amount of the staked tokens.\n     */\n    event Staked(address indexed user, uint256 indexed amount);\n\n    /**\n     * @notice Emitted when a participant unstakes tokens.\n     * @param user address of the participant who unstaked.\n     * @param amount of the unstaked tokens.\n     */\n    event Unstaked(address indexed user, uint256 indexed amount);\n\n    /**\n     * @notice emitted when the expiration date of a lockup is updated.\n     * @param oldDate The previous expiration date of the lockup.\n     * @param newDate The new expiration date of the lockup.\n     */\n    event LockupExpirationDateUpdated(uint256 indexed oldDate, uint256 indexed newDate);\n\n    /**\n     * @notice Address of the Holding Manager contract.\n     * @dev The Holding Manager is responsible for creating and managing user Holdings.\n     */\n    function holdingManager() external view returns (IHoldingManager);\n\n    /**\n     * @notice Address of the underlying asset used for staking.\n     */\n    function underlyingAsset() external view returns (address);\n\n    /**\n     * @notice Address of the reward token distributed for staking.\n     */\n    function rewardToken() external view returns (address);\n\n    /**\n     * @notice Address of the Ion Pool contract.\n     */\n    function ionPool() external view returns (address);\n\n    /**\n     * @notice Address of the Staker contract used for jPoints distribution.\n     */\n    function staker() external view returns (address);\n\n    /**\n     * @notice Represents the expiration date for the staking lockup period.\n     * After this date, staked funds can be withdrawn.\n     * @notice If not withdrawn will continue to generate rewards in `underlyingAsset` and,\n     * if applicable, additional jPoints as long as staked.\n     *\n     * @return The expiration date for the staking lockup period, in Unix timestamp format.\n     */\n    function lockupExpirationDate() external view returns (uint256);\n\n    /**\n     * @notice Stakes a specified amount of assets for the msg.sender.\n     * @dev Initiates the staking operation by transferring the specified `_amount` from the user's wallet to the\n     * contract, while simultaneously recording this deposit within the Jigsaw `Staker` Contract.\n     *\n     * @notice Requirements:\n     * - The caller must have sufficient assets to stake.\n     * - The Ion `Pool` Contract's supply cap should not exceed its limit after the user's stake operation.\n     * - Prior approval is required for this contract to transfer assets on behalf of the user.\n     *\n     * @notice Effects:\n     * - If the user does not have an existing holding, a new holding is created for the user.\n     * - Supplies the specified amount of underlying asset to the Ion's `Pool` Contract to earn interest.\n     * - Tracks the deposit in the `Staker` Contract to earn jPoints for staking.\n     *\n     * @notice Emits:\n     * - `Staked` event indicating the staking action.\n     *\n     * @param _amount of assets to stake.\n     */\n    function stake(uint256 _amount) external;\n\n    /**\n     * @notice Performs unstake operation.\n     * @dev Initiates the withdrawal of staked assets by transferring all the deposited assets plus generated yield from\n     * the Ion's `Pool` Contract and earned jPoint rewards from `Staker` Contract to the designated recipient `_to`.\n     *\n     * @notice Requirements:\n     * - The `lockupExpirationDate` should have already expired.\n     * - The caller must possess sufficient staked assets to fulfill the withdrawal.\n     * - The `_to` address must be a valid Ethereum address.\n     *\n     * @notice Effects:\n     * - Unstakes deposited and accrued underlying assets from Ion's `Pool` Contract.\n     * - Withdraws jPoint rewards from `Staker` Contract.\n     * - Withdraws Ion rewards from `Holding` through `HoldingManager` Contract.\n     *\n     *\n     * @notice Emits:\n     * - `Unstaked` event indicating the unstaking action.\n     *\n     * @param _to address to receive the unstaked assets.\n     */\n    function unstake(address _to) external;\n\n    /**\n     * @notice Triggers stopped state.\n     */\n    function pause() external;\n\n    /**\n     * @notice Returns to normal state.\n     */\n    function unpause() external;\n\n    /**\n     * @notice Renounce ownership override to prevent accidental loss of contract ownership.\n     * @dev This function ensures that the contract's ownership cannot be lost unintentionally.\n     */\n    function renounceOwnership() external pure;\n\n    /**\n     * @notice Allows the default admin role to set a new lockup expiration date.\n     *\n     * @notice Requirements:\n     * - Caller must be `Owner`.\n     * - `_newDate` should be different from `lockupExpirationDate`.\n     *\n     * @notice Effects:\n     * - Sets `lockupExpirationDate` to `_newDate`.\n     *\n     *  @notice Emits:\n     * - `LockupExpirationDateUpdated` event indicating that lockup expiration date has been updated.\n     *\n     * @param _newDate The new lockup expiration date to be set.\n     */\n    function setLockupExpirationDate(uint256 _newDate) external;\n\n    /**\n     * @notice Get the address of the holding associated with the user.\n     * @param _user The address of the user.\n     * @return the holding address.\n     */\n    function getUserHolding(address _user) external view returns (address);\n}\n"
      },
      "src/interfaces/IStaker.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\n/// @title Interface for Staker contract\ninterface IStaker {\n    // --- Errors ---\n    /**\n     * @notice The operation failed because provided address is invalid.\n     */\n    error InvalidAddress();\n\n    /**\n     * @notice The operation failed because provided amount is invalid.\n     */\n    error InvalidAmount();\n\n    /**\n     * @notice The operation failed because caller was unauthorized for the action.\n     */\n    error UnauthorizedCaller();\n\n    /**\n     * @notice The operation failed because the previous rewards period must end first.\n     * @param timestamp The current timestamp when the error occurred.\n     * @param periodFinish The timestamp when the current rewards period is expected to end.\n     */\n    error PreviousPeriodNotFinished(uint256 timestamp, uint256 periodFinish);\n\n    /**\n     * @notice The operation failed because rewards duration is zero.\n     */\n    error ZeroRewardsDuration();\n\n    /**\n     * @notice The operation failed because reward rate was zero.\n     * Caused by an insufficient amount of rewards provided.\n     */\n    error RewardAmountTooSmall();\n\n    /**\n     * @notice The operation failed because reward rate is too big.\n     */\n    error RewardRateTooBig();\n\n    /**\n     * @notice The operation failed because there were no rewards to distribute.\n     */\n    error NoRewardsToDistribute();\n\n    /**\n     * @notice The operation failed because deposit surpasses the supply limit.\n     * @param _amount of tokens attempting to be deposited.\n     * @param supplyLimit allowed for deposits.\n     */\n    error DepositSurpassesSupplyLimit(uint256 _amount, uint256 supplyLimit);\n\n    /**\n     * @notice The operation failed because user doesn't have rewards to claim.\n     */\n    error NothingToClaim();\n\n    /**\n     * @notice The operation failed because renouncing ownership is prohibited.\n     */\n    error RenouncingOwnershipProhibited();\n\n    // --- Events ---\n\n    /**\n     * @notice Event emitted when the rewards duration is updated.\n     * @param oldDuration The previous rewards duration.\n     * @param newDuration The new rewards duration.\n     */\n    event RewardsDurationUpdated(uint256 indexed oldDuration, uint256 indexed newDuration);\n\n    /**\n     * @notice Event emitted when new rewards are added.\n     * @param reward The amount of rewards added.\n     */\n    event RewardAdded(uint256 indexed reward);\n\n    /**\n     * @notice Event emitted when a participant deposits an amount.\n     * @param user The address of the participant who made the deposit.\n     * @param amount The amount that was deposited.\n     */\n    event Staked(address indexed user, uint256 indexed amount);\n\n    /**\n     * @notice Event emitted when a participant withdraws their stake.\n     * @param user The address of the participant who withdrew their stake.\n     * @param amount The amount that was withdrawn.\n     */\n    event Withdrawn(address indexed user, uint256 indexed amount);\n\n    /**\n     * @notice Event emitted when a participant claims their rewards.\n     * @param user The address of the participant who claimed the rewards.\n     * @param reward The amount of rewards that were claimed.\n     */\n    event RewardPaid(address indexed user, uint256 indexed reward);\n\n    /**\n     * @notice returns staking token address.\n     */\n    function tokenIn() external view returns (address);\n\n    /**\n     * @notice returns reward token address.\n     */\n    function rewardToken() external view returns (address);\n\n    /**\n     * @notice returns stakingManager address.\n     */\n    function stakingManager() external view returns (address);\n\n    /**\n     * @notice when current contract distribution ends (block timestamp + rewards duration).\n     */\n    function periodFinish() external view returns (uint256);\n\n    /**\n     * @notice rewards per second.\n     */\n    function rewardRate() external view returns (uint256);\n\n    /**\n     * @notice reward period.\n     */\n    function rewardsDuration() external view returns (uint256);\n\n    /**\n     * @notice last reward update timestamp.\n     */\n    function lastUpdateTime() external view returns (uint256);\n\n    /**\n     * @notice reward-token share.\n     */\n    function rewardPerTokenStored() external view returns (uint256);\n\n    /**\n     * @notice rewards paid to participants so far.\n     */\n    function userRewardPerTokenPaid(address participant) external view returns (uint256);\n\n    /**\n     * @notice accrued rewards per participant.\n     */\n    function rewards(address participant) external view returns (uint256);\n\n    /**\n     * @notice sets the new rewards duration.\n     */\n    function setRewardsDuration(uint256 _rewardsDuration) external;\n\n    /**\n     * @notice Adds more rewards to the contract.\n     *\n     * @dev Prior approval is required for this contract to transfer rewards from `_from` address.\n     *\n     * @param _from address to transfer rewards from.\n     * @param _amount The amount of new rewards.\n     */\n    function addRewards(address _from, uint256 _amount) external;\n\n    /**\n     * @notice Triggers stopped state.\n     */\n    function pause() external;\n\n    /**\n     * @notice Returns to normal state.\n     */\n    function unpause() external;\n\n    /**\n     * @notice returns the total tokenIn supply.\n     */\n    function totalSupply() external view returns (uint256);\n\n    /**\n     * @notice returns total invested amount for an account.\n     * @param _account participant address\n     */\n    function balanceOf(address _account) external view returns (uint256);\n\n    /**\n     * @notice returns the last time rewards were applicable.\n     */\n    function lastTimeRewardApplicable() external view returns (uint256);\n\n    /**\n     * @notice returns rewards per tokenIn.\n     */\n    function rewardPerToken() external view returns (uint256);\n\n    /**\n     * @notice rewards accrued rewards for account.\n     *  @param _account participant's address\n     */\n    function earned(address _account) external view returns (uint256);\n\n    /**\n     * @notice returns reward amount for a specific time range.\n     */\n    function getRewardForDuration() external view returns (uint256);\n\n    /**\n     * @notice Performs a deposit operation for `_user`.\n     * @dev Updates participants' rewards.\n     *\n     * @param _user to deposit for.\n     * @param _amount to deposit.\n     */\n    function deposit(address _user, uint256 _amount) external;\n\n    /**\n     * @notice Withdraws specified `_amount` and claims rewards for the `_user`.\n     * @dev This function enables the caller to exit the investment and claim their rewards.\n     *\n     * @param _user to withdraw and claim for.\n     * @param _to address to which funds will be sent.\n     */\n    function exit(address _user, address _to) external;\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/utils/Context.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * ==== Security Considerations\n *\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\n * generally recommended is:\n *\n * ```solidity\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\n *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\n *     doThing(..., value);\n * }\n *\n * function doThing(..., uint256 value) public {\n *     token.safeTransferFrom(msg.sender, address(this), value);\n *     ...\n * }\n * ```\n *\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\n * {SafeERC20-safeTransferFrom}).\n *\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\n * contracts should have entry points that don't rely on permit.\n */\ninterface IERC20Permit {\n    /**\n     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n     * given ``owner``'s signed approval.\n     *\n     * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n     * ordering also apply here.\n     *\n     * Emits an {Approval} event.\n     *\n     * Requirements:\n     *\n     * - `spender` cannot be the zero address.\n     * - `deadline` must be a timestamp in the future.\n     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n     * over the EIP712-formatted function arguments.\n     * - the signature must use ``owner``'s current nonce (see {nonces}).\n     *\n     * For more information on the signature format, see the\n     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n     * section].\n     *\n     * CAUTION: See Security Considerations above.\n     */\n    function permit(\n        address owner,\n        address spender,\n        uint256 value,\n        uint256 deadline,\n        uint8 v,\n        bytes32 r,\n        bytes32 s\n    ) external;\n\n    /**\n     * @dev Returns the current nonce for `owner`. This value must be\n     * included whenever a signature is generated for {permit}.\n     *\n     * Every successful call to {permit} increases ``owner``'s nonce by one. This\n     * prevents a signature from being used multiple times.\n     */\n    function nonces(address owner) external view returns (uint256);\n\n    /**\n     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n     */\n    // solhint-disable-next-line func-name-mixedcase\n    function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"
      },
      "lib/openzeppelin-contracts/contracts/utils/Address.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n    /**\n     * @dev The ETH balance of the account is not enough to perform the operation.\n     */\n    error AddressInsufficientBalance(address account);\n\n    /**\n     * @dev There's no code at `target` (it is not a contract).\n     */\n    error AddressEmptyCode(address target);\n\n    /**\n     * @dev A call to an address target failed. The target may have reverted.\n     */\n    error FailedInnerCall();\n\n    /**\n     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n     * `recipient`, forwarding all available gas and reverting on errors.\n     *\n     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n     * of certain opcodes, possibly making contracts go over the 2300 gas limit\n     * imposed by `transfer`, making them unable to receive funds via\n     * `transfer`. {sendValue} removes this limitation.\n     *\n     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n     *\n     * IMPORTANT: because control is transferred to `recipient`, care must be\n     * taken to not create reentrancy vulnerabilities. Consider using\n     * {ReentrancyGuard} or the\n     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n     */\n    function sendValue(address payable recipient, uint256 amount) internal {\n        if (address(this).balance < amount) {\n            revert AddressInsufficientBalance(address(this));\n        }\n\n        (bool success, ) = recipient.call{value: amount}(\"\");\n        if (!success) {\n            revert FailedInnerCall();\n        }\n    }\n\n    /**\n     * @dev Performs a Solidity function call using a low level `call`. A\n     * plain `call` is an unsafe replacement for a function call: use this\n     * function instead.\n     *\n     * If `target` reverts with a revert reason or custom error, it is bubbled\n     * up by this function (like regular Solidity function calls). However, if\n     * the call reverted with no returned reason, this function reverts with a\n     * {FailedInnerCall} error.\n     *\n     * Returns the raw returned data. To convert to the expected return value,\n     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n     *\n     * Requirements:\n     *\n     * - `target` must be a contract.\n     * - calling `target` with `data` must not revert.\n     */\n    function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n        return functionCallWithValue(target, data, 0);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but also transferring `value` wei to `target`.\n     *\n     * Requirements:\n     *\n     * - the calling contract must have an ETH balance of at least `value`.\n     * - the called Solidity function must be `payable`.\n     */\n    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n        if (address(this).balance < value) {\n            revert AddressInsufficientBalance(address(this));\n        }\n        (bool success, bytes memory returndata) = target.call{value: value}(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a static call.\n     */\n    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.staticcall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a delegate call.\n     */\n    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.delegatecall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an\n     * unsuccessful call.\n     */\n    function verifyCallResultFromTarget(\n        address target,\n        bool success,\n        bytes memory returndata\n    ) internal view returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            // only check if target is a contract if the call was successful and the return data is empty\n            // otherwise we already know that it was a contract\n            if (returndata.length == 0 && target.code.length == 0) {\n                revert AddressEmptyCode(target);\n            }\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n     * revert reason or with a default {FailedInnerCall} error.\n     */\n    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.\n     */\n    function _revert(bytes memory returndata) private pure {\n        // Look for revert reason and bubble it up if present\n        if (returndata.length > 0) {\n            // The easiest way to bubble the revert reason is using memory via assembly\n            /// @solidity memory-safe-assembly\n            assembly {\n                let returndata_size := mload(returndata)\n                revert(add(32, returndata), returndata_size)\n            }\n        } else {\n            revert FailedInnerCall();\n        }\n    }\n}\n"
      }
    },
    "settings": {
      "remappings": [
        "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
        "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
        "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
        "forge-std/=lib/forge-std/src/",
        "openzeppelin-contracts/=lib/openzeppelin-contracts/"
      ],
      "optimizer": {
        "enabled": true,
        "runs": 300
      },
      "metadata": {
        "useLiteralContent": false,
        "bytecodeHash": "ipfs",
        "appendCBOR": true
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "evmVersion": "shanghai",
      "viaIR": false,
      "libraries": {}
    }
  }}