// File: contracts/strategy/MapleStrategy.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.10;

/******************************************************************************\
* Author: Evert Kors <dev@sherlock.xyz> (https://twitter.com/evert0x)
* Sherlock Protocol: https://sherlock.xyz
/******************************************************************************/

import './base/BaseStrategy.sol';

import '../interfaces/maple/IPool.sol';
import '../interfaces/maple/IMplRewards.sol';

// This contract contains logic for depositing staker funds into maple finance as a yield strategy
// https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol
// https://github.com/maple-labs/maple-core/wiki/Pools

contract MapleStrategy is BaseStrategy {
  using SafeERC20 for IERC20;
  // MPL Reward contract  0x7C57bF654Bc16B0C9080F4F75FF62876f50B8259
  // Current Maven11 USDC pool: 0x6F6c8013f639979C84b756C7FC1500eB5aF18Dc4
  // https://app.maple.finance/#/earn/pool/0x6f6c8013f639979c84b756c7fc1500eb5af18dc4

  // MPL Reward contract 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90
  // Current Orthogonal Trading  USDC pool: 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27
  // https://app.maple.finance/#/earn/pool/0xfebd6f15df3b73dc4307b1d7e65d46413e710c27
  IPool public immutable maplePool;
  IMplRewards public immutable mapleRewards;

  // Address to receive rewards
  address public constant LIQUIDITY_MINING_RECEIVER = 0x666B8EbFbF4D5f0CE56962a25635CfF563F13161;

  /// @param _initialParent Contract that will be the parent in the tree structure
  /// @param _mapleRewards Maple rewards contract linked to USDC staking pool
  constructor(IMaster _initialParent, IMplRewards _mapleRewards) BaseNode(_initialParent) {
    // Get Maple Pool based on the reward pool
    // Store maple pool for future usage
    maplePool = IPool(_mapleRewards.stakingToken());

    // revert if the pool isn't USDC
    if (_initialParent.want() != maplePool.liquidityAsset()) revert InvalidArg();
    // revert if the pool isn't public
    if (maplePool.openToPublic() == false) revert InvalidState();

    // Approve maple mool max amount of USDC
    want.safeIncreaseAllowance(address(maplePool), type(uint256).max);

    // Store maple rewards for future usage
    mapleRewards = _mapleRewards;
  }

  /// @notice Signal if strategy is ready to be used
  /// @return Boolean indicating if strategy is ready
  function setupCompleted() external view override returns (bool) {
    return true;
  }

  /// @notice View timestamp the deposit matures
  /// @dev After this timestamp admin is able to call `withdraw()` if this contract is in the unstake window
  /// @dev Step 1: call `intendToWithdraw()` on `maturityTime` - `stakerCooldownPeriod`
  /// @dev Step 2: when `maturityTime` is reached, the contract is in the unstake window, call `withdraw()` to unstake USDC
  /// @dev https://etherscan.io/address/0xc234c62c8c09687dff0d9047e40042cd166f3600#readContract
  /// @dev stakerCooldownPeriod uint256 :  864000 (10 days to cooldown)
  /// @dev stakerUnstakeWindow  uint256 :  172800 (2 days to unstake)
  function maturityTime() external view returns (uint256) {
    // Get current deposit date from the maple pool
    // Value uses a weigthed average on multiple deposits
    uint256 date = maplePool.depositDate(address(this));

    // Return 0 if no USDC is deposited into the Maple pool
    if (date == 0) return 0;

    // Deposit will mature when lockup period ends
    return date + maplePool.lockupPeriod();
  }

  /// @notice Deposit all USDC in this contract in Maple
  /// @notice Works under the assumption this contract contains USDC
  /// @dev Weighted average is used for depositDate calculation
  /// @dev https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L377
  /// @dev Multiple deposits = weighted average of unlock time https://github.com/maple-labs/maple-core/blob/main/contracts/library/PoolLib.sol#L209
  function _deposit() internal override whenNotPaused {
    // How many maplePool tokens do we currently own
    uint256 maplePoolBalanceBefore = maplePool.balanceOf(address(this));

    // Deposit all USDC into maple
    maplePool.deposit(want.balanceOf(address(this)));

    // How many maplePool tokens did we gain after depositing
    uint256 maplePoolDifference = maplePool.balanceOf(address(this)) - maplePoolBalanceBefore;

    if (maplePoolDifference == 0) return;

    // Approve newly gained maple pool tokens to mapleRewards
    maplePool.increaseCustodyAllowance(address(mapleRewards), maplePoolDifference);

    // "Stake" new tokens in the mapleRewards pool
    // Note that maple pool tokens are not actually transferred
    // https://github.com/maple-labs/mpl-rewards/blob/main/contracts/MplRewards.sol#L87
    mapleRewards.stake(maplePoolDifference);
  }

  /// @notice Send all USDC in this contract to core
  /// @notice Funds need to be withdrawn using `withdrawFromMaple()` first
  /// @return amount Amount of USDC withdrawn
  function _withdrawAll() internal override returns (uint256 amount) {
    // Amount of USDC in the contract
    amount = want.balanceOf(address(this));
    // Transfer USDC to core
    if (amount != 0) want.safeTransfer(core, amount);
  }

  /// @notice Send `_amount` USDC in this contract to core
  /// @notice Funds need to be withdrawn using `withdrawFromMaple()` first
  /// @param _amount Amount of USDC to withdraw
  function _withdraw(uint256 _amount) internal override {
    // Transfer USDC to core
    want.safeTransfer(core, _amount);
  }

  /// @notice View USDC in this contract + USDC in Maple
  /// @dev Important the balance is only increasing after a `claim()` call by the pool admin
  /// @dev This means people can get anticipate these `claim()` calls and get a better entry/exit position in the Sherlock pool
  /// @return Amount of USDC in this strategy
  // Ideally `withdrawableFundsOf` would be incrementing every block
  // This value mostly depends on `accumulativeFundsOf` https://github.com/maple-labs/maple-core/blob/main/contracts/token/BasicFDT.sol#L70
  // Where `pointsPerShare` is the main variable used to increase balance https://github.com/maple-labs/maple-core/blob/main/contracts/token/BasicFDT.sol#L92
  // This variable is mutated in the internal `_distributeFunds` function https://github.com/maple-labs/maple-core/blob/main/contracts/token/BasicFDT.sol#L47
  // This internal function is called by the public `updateFundsReceived()` which depends on `_updateFundsTokenBalance()` to be > 0 https://github.com/maple-labs/maple-core/blob/main/contracts/token/BasicFDT.sol#L179
  // This can only be >0 if `interestSum` mutates to a bigger value https://github.com/maple-labs/maple-core/blob/main/contracts/token/PoolFDT.sol#L51
  // The place where `interestSum` is mutated is the `claim()` function restricted by pool admin / delegate https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L222
  function _balanceOf() internal view override returns (uint256) {
    // Source Lucas Manuel | Maple
    // Even though we 'stake' maple pool tokens in the reward contract
    // They are actually not transferred to the reward contract
    // Maple uses custom 'custody' logic in the staking contract
    // https://github.com/maple-labs/mpl-rewards/blob/main/contracts/MplRewards.sol#L92
    return
      want.balanceOf(address(this)) +
      ((maplePool.balanceOf(address(this)) +
        maplePool.withdrawableFundsOf(address(this)) -
        maplePool.recognizableLossesOf(address(this))) / 10**12);
  }

  /// @notice Start cooldown period for Maple withdrawal
  /// @dev Can only be called by owner
  function intendToWithdraw() external onlyOwner {
    // https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L398
    maplePool.intendToWithdraw();
  }

  /// @notice Withdraw funds to this contract
  /// @dev Can only be called by owner
  /// @notice Actual USDC amount can be bigger or greater based on losses or gains
  /// @notice If `_maplePoolTokenAmount` == `maplePool.balanceOf(address(this)` it will withdraw the max amount of USDC
  /// @notice If `_maplePoolTokenAmount` < `recognizableLossesOf(this)` the transaction will revert
  /// @notice If `_maplePoolTokenAmount` = `recognizableLossesOf(this)`, it will send `withdrawableFundsOf` USDC
  /// @param _maplePoolTokenAmount Amount of maple pool tokens to withdraw (usdc * 10**12)
  function withdrawFromMaple(uint256 _maplePoolTokenAmount) external onlyOwner {
    // Exiting 0 tokens doesn't make sense
    if (_maplePoolTokenAmount == 0) revert ZeroArg();

    // Withdraw all USDC
    if (_maplePoolTokenAmount == type(uint256).max) {
      // Withdraw all maple pool tokens
      _maplePoolTokenAmount = maplePool.balanceOf(address(this));

      // Exiting 0 tokens doesn't make sense
      if (_maplePoolTokenAmount == 0) revert InvalidState();
    }

    // 'Withdraw' maplePool tokens from maple rewards
    // note that maple pool tokens are not actually transferred
    // https://github.com/maple-labs/mpl-rewards/blob/main/contracts/MplRewards.sol#L98
    mapleRewards.withdraw(_maplePoolTokenAmount);

    // Maple pool = 18 decimals
    // USDC = 6 decimals
    // Exchange rate is 1:1
    // Divide by 10**12 to get USDC amount
    uint256 usdcAmount = _maplePoolTokenAmount / 10**12;

    // On withdraw this function is used for the `withdrawableFundsOf()` https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L438
    // As it's calling `_prepareWithdraw()` https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L472
    // Which is using the `withdrawableFundsOf()` function https://github.com/maple-labs/maple-core/blob/main/contracts/token/BasicFDT.sol#L58
    // These earned funds ar send to this contract https://github.com/maple-labs/maple-core/blob/main/contracts/Pool.sol#L476
    // It will automatically add USDC gains and subtract USDC losses.
    // It sends USDC to this contract
    maplePool.withdraw(usdcAmount);
  }

  /// @notice Claim Maple tokens earned by farming
  /// @dev Maple tokens will be send to LIQUIDITY_MINING_RECEIVER
  function claimReward() external {
    // Claim reward tokens
    mapleRewards.getReward();

    // Cache reward token address
    IERC20 rewardToken = mapleRewards.rewardsToken();

    // Query reward token balance
    uint256 balance = rewardToken.balanceOf(address(this));

    // Send all reward tokens to LIQUIDITY_MINING_RECEIVER
    if (balance != 0) rewardToken.safeTransfer(LIQUIDITY_MINING_RECEIVER, balance);
  }
}

// File: contracts/strategy/base/BaseStrategy.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.10;

/******************************************************************************\
* Author: Evert Kors <dev@sherlock.xyz> (https://twitter.com/evert0x)
* Sherlock Protocol: https://sherlock.xyz
/******************************************************************************/

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/security/Pausable.sol';

import '../../interfaces/strategy/INode.sol';
import '../../interfaces/strategy/IStrategy.sol';
import './BaseNode.sol';

abstract contract BaseStrategy is IStrategy, BaseNode, Pausable {
  using SafeERC20 for IERC20;

  /// @dev Return balance of this strategy
  function prepareBalanceCache() external override onlyParent returns (uint256) {
    return _balanceOf();
  }

  /// @dev No cache is used in strategies
  function expireBalanceCache() external override onlyParent {}

  function pause() external virtual onlyOwner {
    _pause();
  }

  function unpause() external virtual onlyOwner {
    _unpause();
  }

  function remove() external virtual override onlyOwner {
    _withdrawAll();
    if (_balanceOf() != 0) revert NonZeroBalance();
    parent.childRemoved();
  }

  function replace(INode _newNode) external virtual override onlyOwner {
    _withdrawAll();
    if (_balanceOf() != 0) revert NonZeroBalance();
    _replace(_newNode);
  }

  function replaceForce(INode _newNode) external virtual override onlyOwner {
    _replace(_newNode);
    emit ForceReplace();
  }
}

// File: contracts/interfaces/maple/IPool.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.10;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IPool is IERC20 {
  // https://github.com/maple-labs/maple-core/blob/main/contracts/token/interfaces/IBaseFDT.sol
  function withdrawableFundsOf(address owner) external view returns (uint256);

  // https://github.com/maple-labs/maple-core/blob/main/contracts/token/interfaces/IExtendedFDT.sol
  function recognizableLossesOf(address) external view returns (uint256);

  function poolDelegate() external view returns (address);

  function poolAdmins(address) external view returns (bool);

  function deposit(uint256) external;

  function increaseCustodyAllowance(address, uint256) external;

  function transferByCustodian(
    address,
    address,
    uint256
  ) external;

  function poolState() external view returns (uint256);

  function deactivate() external;

  function finalize() external;

  function claim(address, address) external returns (uint256[7] memory);

  function setLockupPeriod(uint256) external;

  function setStakingFee(uint256) external;

  function setPoolAdmin(address, bool) external;

  function fundLoan(
    address,
    address,
    uint256
  ) external;

  function withdraw(uint256) external;

  function superFactory() external view returns (address);

  function triggerDefault(address, address) external;

  function isPoolFinalized() external view returns (bool);

  function setOpenToPublic(bool) external;

  function setAllowList(address, bool) external;

  function allowedLiquidityProviders(address) external view returns (bool);

  function openToPublic() external view returns (bool);

  function intendToWithdraw() external;

  function DL_FACTORY() external view returns (uint8);

  function liquidityAsset() external view returns (IERC20);

  function liquidityLocker() external view returns (address);

  function stakeAsset() external view returns (address);

  function stakeLocker() external view returns (address);

  function stakingFee() external view returns (uint256);

  function delegateFee() external view returns (uint256);

  function principalOut() external view returns (uint256);

  function liquidityCap() external view returns (uint256);

  function lockupPeriod() external view returns (uint256);

  function depositDate(address) external view returns (uint256);

  function debtLockers(address, address) external view returns (address);

  function withdrawCooldown(address) external view returns (uint256);

  function setLiquidityCap(uint256) external;

  function cancelWithdraw() external;

  function reclaimERC20(address) external;

  function BPTVal(
    address,
    address,
    address,
    address
  ) external view returns (uint256);

  function isDepositAllowed(uint256) external view returns (bool);

  function getInitialStakeRequirements()
    external
    view
    returns (
      uint256,
      uint256,
      bool,
      uint256,
      uint256
    );
}

// File: contracts/interfaces/maple/IMplRewards.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.10;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

/// @title MplRewards Synthetix farming contract fork for liquidity mining.
interface IMplRewards {
  /**
        @dev   Emits an event indicating that a reward was added.
        @param reward The amount of the added reward.
     */
  event RewardAdded(uint256 reward);

  /**
        @dev   Emits an event indicating that an account has staked.
        @param account The address of the account.
        @param amount  The amount staked.
     */
  event Staked(address indexed account, uint256 amount);

  /**
        @dev   Emits an event indicating that reward was withdrawn.
        @param account The address of the account.
        @param amount  The amount withdrawn.
     */
  event Withdrawn(address indexed account, uint256 amount);

  /**
        @dev   Emits an event indicating that some reward was paid to an account.
        @param account The address of the account rewarded.
        @param reward  The amount rewarded to `account`.
     */
  event RewardPaid(address indexed account, uint256 reward);

  /**
        @dev   Emits an event indicating that the duration of the ward period has updated.
        @param newDuration The new duration of the rewards.
     */
  event RewardsDurationUpdated(uint256 newDuration);

  /**
        @dev   Emits an event indicating that some token was recovered.
        @param token  The address of the token recovered.
        @param amount The amount recovered.
     */
  event Recovered(address token, uint256 amount);

  /**
        @dev   Emits an event indicating that pause state has changed.
        @param isPaused Whether the contract is paused.
     */
  event PauseChanged(bool isPaused);

  /**
        @dev The rewards token.
     */
  function rewardsToken() external view returns (IERC20);

  /**
        @dev The staking token.
     */
  function stakingToken() external view returns (address);

  /**
        @dev The period finish.
     */
  function periodFinish() external view returns (uint256);

  /**
        @dev The rewards rate.
     */
  function rewardRate() external view returns (uint256);

  /**
        @dev The rewards duration.
     */
  function rewardsDuration() external view returns (uint256);

  /**
        @dev The last update time.
     */
  function lastUpdateTime() external view returns (uint256);

  /**
        @dev The reward per token stored.
     */
  function rewardPerTokenStored() external view returns (uint256);

  /**
        @dev The last pause time.
     */
  function lastPauseTime() external view returns (uint256);

  /**
        @dev Whether the contract is paused.
     */
  function paused() external view returns (bool);

  /**
        @param account The address of an account.
        @return The reward per token paid for `account`.
     */
  function userRewardPerTokenPaid(address account) external view returns (uint256);

  /**
        @param account The address of an account.
        @return The rewards `account`.
     */
  function rewards(address account) external view returns (uint256);

  /**
        @return The total supply.
     */
  function totalSupply() external view returns (uint256);

  /**
        @param account The address of an account.
        @return The balance of `account`.
     */
  function balanceOf(address account) external view returns (uint256);

  /**
        @return The last time rewards were applicable.
     */
  function lastTimeRewardApplicable() external view returns (uint256);

  /**
        @return The reward per token.
     */
  function rewardPerToken() external view returns (uint256);

  /**
        @param account The address of an account.
        @return The rewards earned of `account`.
     */
  function earned(address account) external view returns (uint256);

  /**
        @return The reward for a duration.
     */
  function getRewardForDuration() external view returns (uint256);

  /**
        @dev   It emits a `Staked` event.
        @param amount An amount to stake.
     */
  function stake(uint256 amount) external;

  /**
        @dev   It emits a `Withdrawn` event.
        @param amount An amount to withdraw.
     */
  function withdraw(uint256 amount) external;

  /**
        @dev It emits a `RewardPaid` event if any rewards are received.
     */
  function getReward() external;

  /**
        @dev Withdraw the entire balance and get the reward.
     */
  function exit() external;

  /**
        @dev Only the contract Owner may call this.
        @dev It emits a `RewardAdded` event.
        @param reward A reward amount.
     */
  function notifyRewardAmount(uint256 reward) external;

  /**
        @dev End rewards emission earlier. Only the contract Owner may call this.
        @param timestamp A unix timestamp to finish rewards.
     */
  function updatePeriodFinish(uint256 timestamp) external;

  /**
        @dev Added to support recovering tokens unintentionally sent to this contract.
        @dev Only the contract Owner may call this.
        @dev It emits a `Recovered` event.
        @param tokenAddress The address of a token to recover.
        @param tokenAmount  The amount to recover.
     */
  function recoverERC20(address tokenAddress, uint256 tokenAmount) external;

  /**
        @dev   Only the contract Owner may call this.
        @dev   It emits a `RewardsDurationUpdated` event.
        @param _rewardsDuration The new duration for rewards.
     */
  function setRewardsDuration(uint256 _rewardsDuration) external;

  /**
        @dev Change the paused state of the contract. Only the contract Owner may call this.
        @dev It emits a `PauseChanged` event.
        @param _paused Whether to pause the contract.
     */
  function setPaused(bool _paused) external;
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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 Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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);
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

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

// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.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;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    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));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    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");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @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");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// File: @openzeppelin/contracts/security/Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        require(paused(), "Pausable: not paused");
        _;
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

// File: contracts/interfaces/strategy/INode.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.10;

/******************************************************************************\
* Author: Evert Kors <dev@sherlock.xyz> (https://twitter.com/evert0x)
* Sherlock Protocol: https://sherlock.xyz
/******************************************************************************/

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface INode {
  event AdminWithdraw(uint256 amount);
  event ReplaceAsChild();
  event ParentUpdate(IMaster previous, IMaster current);
  event Obsolete(INode implementation);
  event ForceReplace();
  event Replace(INode newAddress);

  error NotImplemented(bytes4 func);
  error SenderNotParent();
  error SenderNotChild();
  error InvalidParent();
  error InvalidCore();
  error InvalidWant();
  error InvalidState();
  error ZeroArg();
  error InvalidArg();
  error NotSetup();
  error IsMaster();
  error BothChild();
  error NotChild();
  error InvalidParentAddress();
  error SetupNotCompleted(INode instance);
  error NonZeroBalance();

  /*//////////////////////////////////////////////////////////////
                        CONSTRUCTOR VARIABLES
  //////////////////////////////////////////////////////////////*/

  /// @return Returns the token type being deposited into a node
  function want() external view returns (IERC20);

  /// @notice Parent will always inherit IMaster interface.
  /// @notice Parent of root node will inherit IStrategyManager
  function parent() external view returns (IMaster);

  /// @notice View core controller of funds
  function core() external view returns (address);

  /*//////////////////////////////////////////////////////////////
                        TREE STRUCTURE LOGIC
  //////////////////////////////////////////////////////////////*/

  /// @notice Replace the node
  /// @notice If this is executed on a strategy, the funds will be withdrawn
  /// @notice If this is executed on a splitter, the children are expected to be the same
  function replace(INode _node) external;

  /// @notice Replace the node
  /// @notice If this is executed on a strategy, attempt is made to withdraw the funds
  /// @notice If this is executed on a splitter, check of children is skipped
  function replaceForce(INode _node) external;

  function setupCompleted() external view returns (bool);

  /// @notice Move the current node as the child of `_node`
  function replaceAsChild(ISplitter _node) external;

  /// @notice Update parent of node
  /// @dev Can only be called by current parent
  function updateParent(IMaster _node) external;

  function siblingRemoved() external;

  /*//////////////////////////////////////////////////////////////
                        YIELD STRATEGY LOGIC
  //////////////////////////////////////////////////////////////*/

  /// @return Returns the token balance managed by this contract
  /// @dev For Splitter this will be the sum of balances of the children
  function balanceOf() external view returns (uint256);

  /// @notice Withdraws all tokens back into core.
  /// @return The final amount withdrawn
  function withdrawAll() external returns (uint256);

  /// @notice Withdraws all token from the node back into core
  /// @return The final amount withdrawn
  function withdrawAllByAdmin() external returns (uint256);

  /// @notice Withdraws a specific amount of tokens from the node back into core
  /// @param _amount Amount of tokens to withdraw
  function withdraw(uint256 _amount) external;

  /// @notice Withdraws a specific amount of tokens from the node back into core
  /// @param _amount Amount of tokens to withdraw
  function withdrawByAdmin(uint256 _amount) external;

  /// @notice Deposits all tokens held in this contract into the children on strategy
  /// @dev Splitter will deposit the tokens in their children
  /// @dev Strategy will deposit the tokens into a yield strategy
  function deposit() external;

  function prepareBalanceCache() external returns (uint256);

  function expireBalanceCache() external;
}

interface IMaster is INode {
  event ChildOneUpdate(INode previous, INode current);

  /// @notice Call by child if it's needs to be updated
  function updateChild(INode _node) external;

  /// @notice Call by child if removed
  function childRemoved() external;

  function isMaster() external view returns (bool);

  function childOne() external view returns (INode);

  function setInitialChildOne(INode _child) external;
}

interface ISplitter is IMaster {
  event ChildTwoUpdate(INode previous, INode current);

  error InvalidChildOne();
  error InvalidChildTwo();

  function childTwo() external view returns (INode);

  function setInitialChildTwo(INode _child) external;
}

// File: contracts/interfaces/strategy/IStrategy.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.10;

/******************************************************************************\
* Author: Evert Kors <dev@sherlock.xyz> (https://twitter.com/evert0x)
* Sherlock Protocol: https://sherlock.xyz
/******************************************************************************/

import './INode.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IStrategy is INode {
  /// @notice remove a strategy
  function remove() external;
}

// File: contracts/strategy/base/BaseNode.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.10;

/******************************************************************************\
* Author: Evert Kors <dev@sherlock.xyz> (https://twitter.com/evert0x)
* Sherlock Protocol: https://sherlock.xyz
/******************************************************************************/

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

import '../../interfaces/strategy/INode.sol';

// Interface used by every node
abstract contract BaseNode is INode, Ownable {
  using SafeERC20 for IERC20;

  // Parent node
  IMaster public override parent;
  // Which token the strategy uses (USDC)
  IERC20 public immutable override want;
  // Reference to core (Sherlock.sol)
  address public immutable override core;

  /// @param _initialParent The initial parent of this node
  constructor(IMaster _initialParent) {
    if (address(_initialParent) == address(0)) revert ZeroArg();

    IERC20 _want = _initialParent.want();
    address _core = _initialParent.core();

    if (address(_want) == address(0)) revert InvalidWant();
    if (address(_core) == address(0)) revert InvalidCore();

    want = _want;
    core = _core;
    parent = _initialParent;

    emit ParentUpdate(IMaster(address(0)), _initialParent);
  }

  modifier onlyParent() {
    if (msg.sender != address(parent)) revert SenderNotParent();
    _;
  }

  /*//////////////////////////////////////////////////////////////
                        TREE STRUCTURE LOGIC
  //////////////////////////////////////////////////////////////*/

  /// @notice Replace this node to be a child of `_newParent`
  /// @param _newParent address of the new parent
  /// @dev Replace as child ensures that (this) is the child of the `_newParent`
  /// @dev It will also enfore a `_executeParentUpdate` to make that relation bi-directional
  /// @dev For the other child is does minimal checks, it only checks if it isn't the same as address(this)
  function replaceAsChild(ISplitter _newParent) external virtual override onlyOwner {
    /*
          m
          |
        this

          m
          |
          1
         / \
        z  this
    */

    // Gas savings
    IMaster _currentParent = parent;

    // Revert is parent is master
    // The master is always at the root of the tree
    if (_newParent.isMaster()) revert IsMaster();

    // Verify if the new parent has the right connections
    _verifyParentUpdate(_currentParent, _newParent);
    // Verify is childs of newParent are correct
    INode otherChild = _verifyNewParent(_newParent);

    // Revert if otherchild = 0
    // Revert if the other child has the right parent reference too
    // Check if `z` has the right parent (referencing comment on top function)
    if (otherChild.parent() != _newParent) revert InvalidParent();

    // Check if `_newParent` references our currentParent as their parent
    // Check if `m` == `1`.parent() (referencing comment on top function)
    if (_currentParent != _newParent.parent()) revert InvalidParent();

    // Make sure the parent recognizes the new child
    // Make sure `m` references `1` as it's child (referencing comment on top function)
    _currentParent.updateChild(_newParent);

    // Update parent
    _executeParentUpdate(_currentParent, _newParent);

    emit ReplaceAsChild();
  }

  /// @notice Replace parent of this node
  /// @param _newParent Address of the new parent
  /// @dev Only callable by current parent
  function updateParent(IMaster _newParent) external virtual override onlyParent {
    // Verify if the parent can be updated
    _verifyParentUpdate(IMaster(msg.sender), _newParent);
    _verifyNewParent(_newParent);

    // Update parent
    _executeParentUpdate(IMaster(msg.sender), _newParent);
  }

  /// @notice Get notified by parent that your sibling is removed
  /// @dev This contract will take the position of the parent
  /// @dev Only callable by current parent
  function siblingRemoved() external override onlyParent {
    // Get current parent of parent
    IMaster _newParent = parent.parent();

    // Take position of current parent
    _verifyParentUpdate(IMaster(msg.sender), _newParent);
    // NOTE: _verifyNewParent() is skipped on this call
    // As address(this) should be added as a child after the function returns
    _executeParentUpdate(IMaster(msg.sender), _newParent);
  }

  /// @notice Verify if `_newParent` is able to be our new parent
  /// @param _newParent Address of the new parent
  /// @return otherChild Address of the child that isn't address(this)
  function _verifyNewParent(IMaster _newParent) internal view returns (INode otherChild) {
    // The setup needs to be completed of parent
    if (_newParent.setupCompleted() == false) revert SetupNotCompleted(_newParent);

    // get first child
    INode firstChild = _newParent.childOne();
    INode secondChild;

    // is address(this) childOne?
    bool isFirstChild = address(firstChild) == address(this);
    bool isSecondChild = false;

    // Parent only has a childTwo if it isn't master
    if (!_newParent.isMaster()) {
      // get second child
      secondChild = ISplitter(address(_newParent)).childTwo();
      // is address(this) childTwo?
      isSecondChild = address(secondChild) == address(this);
    }

    // Check if address(this) is referenced as both childs
    if (isFirstChild && isSecondChild) revert BothChild();
    // Check if address(this) isn't referenced at all
    if (!isFirstChild && !isSecondChild) revert NotChild();

    // return child that isn't address(this)
    if (isFirstChild) {
      return secondChild;
    }
    return firstChild;
  }

  /// @notice Verify if `_newParent` can replace `_currentParent`
  /// @param _currentParent Address of our current `parent`
  /// @param _newParent Address of our future `parent`
  function _verifyParentUpdate(IMaster _currentParent, IMaster _newParent) internal view {
    // Revert if it's the same address
    if (address(_newParent) == address(this)) revert InvalidParentAddress();
    // Revert if the address is parent
    if (address(_newParent) == address(_currentParent)) revert InvalidParentAddress();
    // Revert if core is invalid
    if (_currentParent.core() != _newParent.core()) revert InvalidCore();
    // Revert if want is invalid
    if (_currentParent.want() != _newParent.want()) revert InvalidWant();
  }

  /// @notice Set parent in storage
  /// @param _currentParent Address of our current `parent`
  /// @param _newParent Address of our future `parent`
  function _executeParentUpdate(IMaster _currentParent, IMaster _newParent) internal {
    // Make `_newParent` our new parent
    parent = _newParent;
    emit ParentUpdate(_currentParent, _newParent);
  }

  /// @notice Replace address(this) with `_newNode`
  function _replace(INode _newNode) internal {
    if (address(_newNode) == address(0)) revert ZeroArg();
    if (_newNode.setupCompleted() == false) revert SetupNotCompleted(_newNode);
    if (address(_newNode) == address(this)) revert InvalidArg();
    if (_newNode.parent() != parent) revert InvalidParent();
    if (_newNode.core() != core) revert InvalidCore();
    if (_newNode.want() != want) revert InvalidWant();

    // Make sure our parent references `_newNode` as it's child
    parent.updateChild(_newNode);

    emit Replace(_newNode);
    emit Obsolete(INode(address(this)));
  }

  /*//////////////////////////////////////////////////////////////
                        YIELD STRATEGY LOGIC
  //////////////////////////////////////////////////////////////*/

  function balanceOf() external view override returns (uint256 amount) {
    return _balanceOf();
  }

  function withdrawAll() external override onlyParent returns (uint256 amount) {
    amount = _withdrawAll();
  }

  function withdrawAllByAdmin() external override onlyOwner returns (uint256 amount) {
    amount = _withdrawAll();
    emit AdminWithdraw(amount);
  }

  function withdraw(uint256 _amount) external override onlyParent {
    if (_amount == 0) revert ZeroArg();

    _withdraw(_amount);
  }

  function withdrawByAdmin(uint256 _amount) external override onlyOwner {
    if (_amount == 0) revert ZeroArg();

    _withdraw(_amount);
    emit AdminWithdraw(_amount);
  }

  function deposit() external override onlyParent {
    _deposit();
  }

  function _balanceOf() internal view virtual returns (uint256 amount) {}

  function _withdrawAll() internal virtual returns (uint256 amount) {}

  function _withdraw(uint256 _amount) internal virtual {}

  function _deposit() internal virtual {}
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
    }
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @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
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason 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 {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}