// File: src/pendle/accumulator/PendleAccumulatorV2.sol



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

import "src/base/interfaces/ILiquidityGauge.sol";
import "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {ILocker} from "src/base/interfaces/ILocker.sol";
import {PendleLocker} from "src/pendle/locker/PendleLocker.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {ISDTDistributor} from "src/base/interfaces/ISDTDistributor.sol";
import {IPendleFeeDistributor} from "src/base/interfaces/IPendleFeeDistributor.sol";

interface IWETH {
    function deposit() external payable;
}

/// @title A contract that accumulates ETH rewards and notifies them to the LGV4
/// @author StakeDAO
contract PendleAccumulatorV2 {
    using SafeTransferLib for IERC20;

    // Errors
    error DIFFERENT_LENGTH();
    error FEE_TOO_HIGH();
    error NO_BALANCE();
    error NO_REWARD();
    error NOT_ALLOWED();
    error NOT_ALLOWED_TO_PULL();
    error NOT_CLAIMED_ALL();
    error ONGOING_REWARD();
    error WRONG_CLAIM();
    error ZERO_ADDRESS();

    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant VE_PENDLE = 0x4f30A9D41B80ecC5B94306AB4364951AE3170210;
    address public constant PENDLE_FEE_D = 0x8C237520a8E14D658170A633D96F8e80764433b9;

    // fee recipients
    address public bountyRecipient;
    address public daoRecipient;
    address public veSdtFeeProxy;
    address public votesRewardRecipient;
    uint256 public bountyFee;
    uint256 public daoFee;
    uint256 public veSdtFeeProxyFee;
    uint256 public claimerFee;

    address public governance;
    address public locker = 0xD8fa8dC5aDeC503AcC5e026a98F32Ca5C1Fa289A;
    address public gauge = 0x50DC9aE51f78C593d4138263da7088A973b8184E;
    address public sdtDistributor;

    uint256 public periodsToAdd = 4;
    /// @notice weth rewards period to notify
    uint256 public periodsToNotify;

    mapping(uint256 => uint256) public rewards; // period -> reward amount
    mapping(address => uint256) public canPullTokens;

    /// @notice If set, the voters rewards will be distributed to the gauge
    bool public distributeVotersRewards;

    // Events
    event BountyFeeSet(uint256 _old, uint256 _new);
    event BountyRecipientSet(address _old, address _new);
    event ClaimerFeeSet(uint256 _old, uint256 _new);
    event DaoFeeSet(uint256 _old, uint256 _new);
    event DaoRecipientSet(address _old, address _new);
    event DistributeVotersRewardsSet(bool _distributeAllRewards);
    event ERC20Rescued(address _token, uint256 _amount);
    event GaugeSet(address _old, address _new);
    event GovernanceSet(address _old, address _new);
    event LockerSet(address _old, address _new);
    event PeriodsToAddSet(uint256 _old, uint256 _new);
    event RewardNotified(address _gauge, address _tokenReward, uint256 _amountNotified);
    event SdtDistributorUpdated(address _old, address _new);
    event VeSdtFeeProxyFeeSet(uint256 _old, uint256 _new);
    event VeSdtFeeProxySet(address _old, address _new);

    /* ========== CONSTRUCTOR ========== */
    constructor(
        address _governance,
        address _daoRecipient,
        address _bountyRecipient,
        address _veSdtFeeProxy,
        address _votesRewardRecipient
    ) {
        governance = _governance;
        daoRecipient = _daoRecipient;
        bountyRecipient = _bountyRecipient;
        veSdtFeeProxy = _veSdtFeeProxy;
        votesRewardRecipient = _votesRewardRecipient;
        daoFee = 500; // 5%
        bountyFee = 1000; // 10%
    }

    /* ========== MUTATIVE FUNCTIONS ========== */
    /// @notice Claims Eth rewards via the locker, wrap to WETH and notify it to the LGV4
    function claimForVePendle() external {
        address[] memory pools = new address[](1);
        pools[0] = VE_PENDLE;
        uint256[] memory rewardsClaimable =
            IPendleFeeDistributor(PENDLE_FEE_D).getProtocolClaimables(address(locker), pools);
        /// check if there is any eth to claim for the vePENDLe pool
        if (rewardsClaimable[0] == 0) revert NO_REWARD();
        // reward for 1 months, split the reward in periodsToAdd
        // charge fees once for the entire month
        _chargeFee(_claimReward(pools));
        periodsToNotify += periodsToAdd;
    }

    /// @notice Claims rewards for the voters and send to a recipient
    /// @param _pools pools to claim for
    function claimForVoters(address[] calldata _pools) external {
        // VE_PENDLE pool can't be present
        for (uint256 i; i < _pools.length;) {
            if (_pools[i] == VE_PENDLE) revert WRONG_CLAIM();
            unchecked {
                ++i;
            }
        }
        address[] memory vePendlePool = new address[](1);
        vePendlePool[0] = VE_PENDLE;
        uint256 vePendleRewardClaimable =
            IPendleFeeDistributor(PENDLE_FEE_D).getProtocolClaimables(address(locker), vePendlePool)[0];
        uint256 totalAccrued = IPendleFeeDistributor(PENDLE_FEE_D).getProtocolTotalAccrued(address(locker));
        uint256 claimed = IPendleFeeDistributor(PENDLE_FEE_D).claimed(address(locker));
        uint256 totalReward = _claimReward(_pools);

        if (totalReward + _pools.length != totalAccrued - claimed - vePendleRewardClaimable) revert NOT_CLAIMED_ALL();

        // send the reward to the recipient if it is not to distribute
        // and not charge fees on this
        if (!distributeVotersRewards) {
            IERC20(WETH).transfer(votesRewardRecipient, totalReward);
        } else {
            _chargeFee(totalReward);
        }
    }

    /// @notice Claims rewards for voters and vePendle (all rewarded pools need to be included)
    /// @param _pools pools to claim for
    function claimForAll(address[] memory _pools) external {
        uint256 totalAccrued = IPendleFeeDistributor(PENDLE_FEE_D).getProtocolTotalAccrued(address(locker));
        uint256 claimed = IPendleFeeDistributor(PENDLE_FEE_D).claimed(address(locker));

        uint256 totalReward = _claimReward(_pools);
        if (totalReward + _pools.length != totalAccrued - claimed) revert NOT_CLAIMED_ALL();
        periodsToNotify += periodsToAdd;

        if (!distributeVotersRewards) {
            address[] memory vePendlePool = new address[](1);
            vePendlePool[0] = VE_PENDLE;
            uint256 vePendleRewardClaimable =
                IPendleFeeDistributor(PENDLE_FEE_D).getProtocolClaimables(address(locker), vePendlePool)[0];
            uint256 votersTotalReward = totalReward - vePendleRewardClaimable;
            // transfer the amount without charging fees
            IERC20(WETH).transfer(votesRewardRecipient, votersTotalReward);
            totalReward -= votersTotalReward;
        }
        if (totalReward != 0) {
            _chargeFee(totalReward);
        }
        _notifyReward(WETH);

        _distributeSDT();
    }

    /// @notice Notify the reward already claimed for the current period
    /// @param _token token to notify as reward
    function notifyReward(address _token) external {
        _notifyReward(_token);
        _distributeSDT();
    }

    /// @notice Notify the rewards already claimed for the current period
    /// @param _tokens tokens to notify as reward
    function notifyRewards(address[] memory _tokens) external {
        uint256 tokensLength = _tokens.length;
        for (uint256 i; i < tokensLength;) {
            _notifyReward(_tokens[i]);
            unchecked {
                ++i;
            }
        }
        _distributeSDT();
    }

    /// @notice Pull tokens
    /// @param _tokens tokens to pulls
    /// @param _amounts amounts to transfer to the caller
    function pullTokens(address[] calldata _tokens, uint256[] calldata _amounts) external {
        if (canPullTokens[msg.sender] == 0) revert NOT_ALLOWED_TO_PULL();

        if (_tokens.length != _amounts.length) revert DIFFERENT_LENGTH();

        for (uint256 i = 0; i < _tokens.length; i++) {
            if (_tokens[i] == address(0)) {
                SafeTransferLib.safeTransferETH(msg.sender, _amounts[i]);
            } else {
                SafeTransferLib.safeTransfer(_tokens[i], msg.sender, _amounts[i]);
            }
        }
    }

    /// @notice Claim reward for the pools
    /// @param _pools pools to claim the rewards
    function _claimReward(address[] memory _pools) internal returns(uint256 claimed) {
        uint256 balanceBefore = address(this).balance;
        PendleLocker(locker).claimRewards(address(this), _pools);
        // Wrap Eth to WETH
        claimed = address(this).balance - balanceBefore;
        if (claimed == 0) revert NO_BALANCE();
        IWETH(WETH).deposit{value: address(this).balance}();
    }

    /// @notice Reserve fees for dao, bounty and veSdtFeeProxy
    /// @param _amount amount to charge fees
    function _chargeFee(uint256 _amount) internal {
        // dao part
        if (daoFee > 0) {
            uint256 daoAmount = (_amount * daoFee) / 10_000;
            SafeTransferLib.safeTransfer(WETH, daoRecipient, daoAmount);
        }

        // bounty part
        if (bountyFee > 0) {
            uint256 bountyAmount = (_amount * bountyFee) / 10_000;
            SafeTransferLib.safeTransfer(WETH, bountyRecipient, bountyAmount);
        }

        // veSDTFeeProxy part
        if (veSdtFeeProxyFee > 0) {
            uint256 veSdtFeeProxyAmount = (_amount * veSdtFeeProxyFee) / 10_000;
            SafeTransferLib.safeTransfer(WETH, veSdtFeeProxy, veSdtFeeProxyAmount);
        }
    }

    /// @notice Distribute SDT if there is any
    function _distributeSDT() internal {
        if (sdtDistributor != address(0)) {
            ISDTDistributor(sdtDistributor).distribute(gauge);
        }
    }

    /// @notice Notify the new reward to the LGV4
    /// @param _tokenReward token to notify
    function _notifyReward(address _tokenReward) internal {
        uint256 amountToNotify;
        if (_tokenReward == WETH && periodsToNotify != 0) {
            uint256 currentWeek = block.timestamp * 1 weeks / 1 weeks;
            if (rewards[currentWeek] != 0) revert ONGOING_REWARD();
            amountToNotify = IERC20(WETH).balanceOf(address(this)) / periodsToNotify;
            rewards[currentWeek] = amountToNotify;
            periodsToNotify -= 1;
        } else {
            amountToNotify = IERC20(_tokenReward).balanceOf(address(this));
        }

        if (amountToNotify != 0) {
            if (claimerFee > 0) {
                uint256 claimerReward = (amountToNotify * claimerFee) / 10_000;
                SafeTransferLib.safeTransfer(_tokenReward, msg.sender, claimerReward);
                amountToNotify -= claimerReward;
            }

            IERC20(_tokenReward).approve(gauge, amountToNotify);
            ILiquidityGauge(gauge).deposit_reward_token(_tokenReward, amountToNotify);

            emit RewardNotified(gauge, _tokenReward, amountToNotify);
        }
    }

    /// @notice Set DAO recipient
    /// @param _daoRecipient recipient address
    function setDaoRecipient(address _daoRecipient) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_daoRecipient == address(0)) revert ZERO_ADDRESS();
        emit DaoRecipientSet(daoRecipient, _daoRecipient);
        daoRecipient = _daoRecipient;
    }

    /// @notice Set Bounty recipient
    /// @param _bountyRecipient recipient address
    function setBountyRecipient(address _bountyRecipient) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_bountyRecipient == address(0)) revert ZERO_ADDRESS();
        emit BountyRecipientSet(bountyRecipient, _bountyRecipient);
        bountyRecipient = _bountyRecipient;
    }

    /// @notice Set VeSdtFeeProxy
    /// @param _veSdtFeeProxy proxy address
    function setVeSdtFeeProxy(address _veSdtFeeProxy) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_veSdtFeeProxy == address(0)) revert ZERO_ADDRESS();
        emit VeSdtFeeProxySet(veSdtFeeProxy, _veSdtFeeProxy);
        veSdtFeeProxy = _veSdtFeeProxy;
    }

    /// @notice Set fees reserved to the DAO at every claim
    /// @param _daoFee fee (100 = 1%)
    function setDaoFee(uint256 _daoFee) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_daoFee > 10_000 || _daoFee + bountyFee + veSdtFeeProxyFee + claimerFee > 10_000) {
            revert FEE_TOO_HIGH();
        }
        emit DaoFeeSet(daoFee, _daoFee);
        daoFee = _daoFee;
    }

    /// @notice Set fees reserved to bounty at every claim
    /// @param _bountyFee fee (100 = 1%)
    function setBountyFee(uint256 _bountyFee) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_bountyFee > 10_000 || _bountyFee + daoFee + veSdtFeeProxyFee + claimerFee > 10_000) revert FEE_TOO_HIGH();
        emit BountyFeeSet(bountyFee, _bountyFee);
        bountyFee = _bountyFee;
    }

    /// @notice Set fees reserved to VeSdtFeeProxy at every claim
    /// @param _veSdtFeeProxyFee fee (100 = 1%)
    function setVeSdtFeeProxyFee(uint256 _veSdtFeeProxyFee) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_veSdtFeeProxyFee > 10_000 || _veSdtFeeProxyFee + daoFee + bountyFee + claimerFee > 10_000) {
            revert FEE_TOO_HIGH();
        }
        emit VeSdtFeeProxyFeeSet(veSdtFeeProxyFee, _veSdtFeeProxyFee);
        veSdtFeeProxyFee = _veSdtFeeProxyFee;
    }

    /// @notice Set fees reserved to claimer at every claim
    /// @param _claimerFee (100 = 1%)
    function setClaimerFee(uint256 _claimerFee) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_claimerFee > 10_000) revert FEE_TOO_HIGH();
        emit ClaimerFeeSet(claimerFee, _claimerFee);
        claimerFee = _claimerFee;
    }

    /// @notice Sets gauge for the accumulator which will receive and distribute the rewards
    /// @dev Can be called only by the governance
    /// @param _gauge gauge address
    function setGauge(address _gauge) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_gauge == address(0)) revert ZERO_ADDRESS();
        emit GaugeSet(gauge, _gauge);
        gauge = _gauge;
    }

    /// @notice Sets SdtDistributor to distribute from the Accumulator SDT Rewards to Gauge.
    /// @dev Can be called only by the governance
    /// @param _sdtDistributor gauge address
    function setSdtDistributor(address _sdtDistributor) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_sdtDistributor == address(0)) revert ZERO_ADDRESS();

        emit SdtDistributorUpdated(sdtDistributor, _sdtDistributor);
        sdtDistributor = _sdtDistributor;
    }

    /// @notice Set distribute voter rewards to true/false
    /// @dev Can be called only by the governance
    /// @param _distributeVotersRewards enable/disable reward distribution
    function setDistributeVotersRewards(bool _distributeVotersRewards) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        emit DistributeVotersRewardsSet(distributeVotersRewards = _distributeVotersRewards);
    }

    /// @notice Allows the governance to set the new governance
    /// @dev Can be called only by the governance
    /// @param _governance governance address
    function setGovernance(address _governance) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_governance == address(0)) revert ZERO_ADDRESS();
        emit GovernanceSet(governance, _governance);
        governance = _governance;
    }

    /// @notice Allows the governance to set the locker
    /// @dev Can be called only by the governance
    /// @param _locker locker address
    function setLocker(address _locker) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_locker == address(0)) revert ZERO_ADDRESS();
        emit LockerSet(locker, _locker);
        locker = _locker;
    }

    /// @notice Allows the governance to set rewards periods to add
    /// @dev Can be called only by the governance
    /// @param _periodsToAdd reward period to add at every ve_pendle claim
    function setPeriodsToAdd(uint256 _periodsToAdd) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        emit PeriodsToAddSet(periodsToAdd, _periodsToAdd);
        periodsToAdd = _periodsToAdd;
    }

    /// @notice Toggle the allowance to pull tokens from the contract
    /// @dev Can be called only by the governance
    /// @param _user user to toggle
    function togglePullAllowance(address _user) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        canPullTokens[_user] = canPullTokens[_user] == 0 ? 1 : 0;
    }

    /// @notice A function that rescue any ERC20 token
    /// @param _token token address
    /// @param _amount amount to rescue
    /// @param _recipient address to send token rescued
    function rescueToken(address _token, uint256 _amount, address _recipient) external {
        if (msg.sender != governance) revert NOT_ALLOWED();
        if (_recipient == address(0)) revert ZERO_ADDRESS();

        if (_token == address(0)) {
            SafeTransferLib.safeTransferETH(msg.sender, _amount);
        } else {
            SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
        }

        emit ERC20Rescued(_token, _amount);
    }

    receive() external payable {}
}

// File: src/base/interfaces/ILiquidityGauge.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;

interface ILiquidityGauge {
    struct Reward {
        address token;
        address distributor;
        uint256 period_finish;
        uint256 rate;
        uint256 last_update;
        uint256 integral;
    }

    // solhint-disable-next-line
    function deposit_reward_token(address _rewardToken, uint256 _amount) external;

    // solhint-disable-next-line
    function claim_rewards_for(address _user, address _recipient) external;

    function working_balances(address _address) external view returns (uint256);

    // // solhint-disable-next-line
    // function claim_rewards_for(address _user) external;

    // solhint-disable-next-line
    function deposit(uint256 _value, address _addr) external;

    // solhint-disable-next-line
    function reward_tokens(uint256 _i) external view returns (address);

    // solhint-disable-next-line
    function reward_data(address _tokenReward) external view returns (Reward memory);

    function balanceOf(address) external returns (uint256);

    function claimable_reward(address _user, address _reward_token) external view returns (uint256);

    function claimable_tokens(address _user) external returns (uint256);

    function user_checkpoint(address _user) external returns (bool);

    function commit_transfer_ownership(address) external;

    function claim_rewards(address) external;

    function add_reward(address, address) external;

    function set_claimer(address) external;

    function admin() external view returns (address);

    function set_reward_distributor(address _rewardToken, address _newDistrib) external;

    function initialize(
        address staking_token,
        address admin,
        address SDT,
        address voting_escrow,
        address veBoost_proxy,
        address distributor
    ) external;
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// File: src/base/interfaces/ILocker.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

interface ILocker {
    function createLock(uint256, uint256) external;

    function claimAllRewards(address[] calldata _tokens, address _recipient) external;

    function increaseAmount(uint256) external;

    function increaseAmount(uint128) external;

    function increaseUnlockTime(uint256) external;

    function release() external;

    function claimRewards(address, address) external;

    function claimFXSRewards(address) external;

    function claimFPISRewards(address) external;

    function execute(address, uint256, bytes calldata) external returns (bool, bytes memory);

    function setGovernance(address) external;

    function voteGaugeWeight(address, uint256) external;

    function setAngleDepositor(address) external;

    function setFxsDepositor(address) external;

    function setYieldDistributor(address) external;

    function setGaugeController(address) external;

    function setAccumulator(address _accumulator) external;

    function governance() external view returns (address);

    function increaseLock(uint256 _value, uint256 _duration) external;

    function release(address _recipient) external;
}

// File: src/pendle/locker/PendleLocker.sol



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

import "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts/token/ERC20/IERC20.sol";
import "src/base/interfaces/IVePendle.sol";
import "src/base/interfaces/IPendleFeeDistributor.sol";

/// @title Pendle Locker
/// @author StakeDAO
/// @notice Locks the PENDLE tokens to vePENDLE contract
contract PendleLocker {
    using SafeERC20 for IERC20;

    /* ========== STATE VARIABLES ========== */
    address public governance;
    address public pendleDepositor;
    address public accumulator;

    address public constant TOKEN = 0x808507121B80c02388fAd14726482e061B8da827;
    address public constant VOTING_ESCROW = 0x4f30A9D41B80ecC5B94306AB4364951AE3170210;
    address public feeDistributor = 0x8C237520a8E14D658170A633D96F8e80764433b9;

    /* ========== EVENTS ========== */
    event LockCreated(address indexed user, uint256 value, uint256 duration);
    event TokenClaimed(address indexed user, uint256 value);
    event VotedOnGaugeWeight(address indexed _gauge, uint256 _weight);
    event Released(address indexed user, uint256 value);
    event GovernanceChanged(address indexed newGovernance);
    event PendleDepositorChanged(address indexed newApwDepositor);
    event AccumulatorChanged(address indexed newAccumulator);
    event FeeDistributorChanged(address indexed newFeeDistributor);

    /* ========== CONSTRUCTOR ========== */
    constructor(address _governance, address _accumulator) {
        governance = _governance;
        accumulator = _accumulator;
        IERC20(TOKEN).approve(VOTING_ESCROW, type(uint256).max);
    }

    /* ========== MODIFIERS ========== */
    modifier onlyGovernance() {
        require(msg.sender == governance, "!gov");
        _;
    }

    modifier onlyGovernanceOrAcc() {
        require(msg.sender == governance || msg.sender == accumulator, "!(gov||acc)");
        _;
    }

    modifier onlyGovernanceOrDepositor() {
        require(msg.sender == governance || msg.sender == pendleDepositor, "!(gov||PendleDepositor)");
        _;
    }

    /* ========== MUTATIVE FUNCTIONS ========== */
    /// @notice Creates a lock by locking PENDLE token in the VEPENDLE contract for the specified time
    /// @dev Can only be called by governance or proxy
    /// @param _value The amount of token to be locked
    /// @param _unlockTime The duration for which the token is to be locked
    function createLock(uint128 _value, uint128 _unlockTime) external onlyGovernance {
        IVePendle(VOTING_ESCROW).increaseLockPosition(_value, _unlockTime);
        emit LockCreated(msg.sender, _value, _unlockTime);
    }

    /// @notice Increases the amount of PENDLE locked in VEPENDLE
    /// @dev The PENDLE needs to be transferred to this contract before calling
    /// @param _value The amount by which the lock amount is to be increased
    function increaseAmount(uint128 _value) external onlyGovernanceOrDepositor {
        (, uint128 expiry) = IVePendle(VOTING_ESCROW).positionData(address(this));
        IVePendle(VOTING_ESCROW).increaseLockPosition(_value, expiry);
    }

    /// @notice Increases the duration for which PENDLE is locked in VEPENDLE for the user calling the function
    /// @param _unlockTime The duration in seconds for which the token is to be locked
    function increaseUnlockTime(uint128 _unlockTime) external onlyGovernanceOrDepositor {
        IVePendle(VOTING_ESCROW).increaseLockPosition(0, _unlockTime);
    }

    /// @notice Claim the token reward from the PENDLE fee Distributor passing the tokens as input parameter
    /// @param _recipient The address which will receive the claimed token reward
    function claimRewards(address _recipient, address[] calldata _pools) external onlyGovernanceOrAcc {
        (uint256 totalAmount,) = IPendleFeeDistributor(feeDistributor).claimProtocol(_recipient, _pools);
        emit TokenClaimed(_recipient, totalAmount);
    }

    /// @notice Withdraw the PENDLE from VEPENDLE
    /// @dev call only after lock time expires
    /// @param _recipient The address which will receive the released PENDLE
    function release(address _recipient) external onlyGovernance {
        IVePendle(VOTING_ESCROW).withdraw();
        uint256 balance = IERC20(TOKEN).balanceOf(address(this));

        IERC20(TOKEN).safeTransfer(_recipient, balance);
        emit Released(_recipient, balance);
    }

    /// @notice Set new governance address
    /// @param _governance governance address
    function setGovernance(address _governance) external onlyGovernance {
        governance = _governance;
        emit GovernanceChanged(_governance);
    }

    /// @notice Set the PENDLE Depositor
    /// @param _pendleDepositor PENDLE deppositor address
    function setPendleDepositor(address _pendleDepositor) external onlyGovernance {
        pendleDepositor = _pendleDepositor;
        emit PendleDepositorChanged(_pendleDepositor);
    }

    /// @notice Set the fee distributor
    /// @param _newFD fee distributor address
    function setFeeDistributor(address _newFD) external onlyGovernance {
        feeDistributor = _newFD;
        emit FeeDistributorChanged(_newFD);
    }

    /// @notice Set the accumulator
    /// @param _accumulator accumulator address
    function setAccumulator(address _accumulator) external onlyGovernance {
        accumulator = _accumulator;
        emit AccumulatorChanged(_accumulator);
    }

    /// @notice execute a function
    /// @param to Address to sent the value to
    /// @param value Value to be sent
    /// @param data Call function data
    function execute(address to, uint256 value, bytes calldata data)
        external
        onlyGovernance
        returns (bool, bytes memory)
    {
        (bool success, bytes memory result) = to.call{value: value}(data);
        return (success, result);
    }
}

// File: lib/solady/src/utils/SafeTransferLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for gas griefing protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH
    /// that disallows any storage writes.
    uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    /// Multiply by a small constant (e.g. 2), if needed.
    uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` (in wei) ETH to `to`.
    /// Reverts upon failure.
    ///
    /// Note: This implementation does NOT protect against gas griefing.
    /// Please use `forceSafeTransferETH` for gas griefing protection.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // To coerce gas estimation to provide enough gas for the `create` above.
                    if iszero(gt(gas(), 1000000)) { revert(0, 0) }
                }
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
    /// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
    /// for 99% of cases and can be overridden with the three-argument version of this
    /// function if necessary.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        // Manually inlined because the compiler doesn't inline functions with branches.
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // To coerce gas estimation to provide enough gas for the `create` above.
                    if iszero(gt(gas(), 1000000)) { revert(0, 0) }
                }
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
    ///
    /// Note: Does NOT revert upon failure.
    /// Returns whether the transfer of ETH is successful instead.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            success := call(gasStipend, to, amount, 0, 0, 0, 0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x0c, 0x23b872dd000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x0c, 0x70a08231000000000000000000000000)
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x23b872dd)
            // The `amount` argument is already written to the memory word at 0x60.
            amount := mload(0x60)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x14, to) // Store the `to` argument.
            // The `amount` argument is already written to the memory word at 0x34.
            amount := mload(0x34)
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `approve(address,uint256)`.
            mstore(0x00, 0x095ea7b3000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `ApproveFailed()`.
                mstore(0x00, 0x3e3f8f73)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x00, 0x70a08231000000000000000000000000)
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}

// File: src/base/interfaces/ISDTDistributor.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;

interface ISDTDistributor {
    function distribute(address gaugeAddr) external;
}

// File: src/base/interfaces/IPendleFeeDistributor.sol



pragma solidity 0.8.7;

interface IPendleFeeDistributor {
    // ========================= STRUCT =========================
    struct UpdateProtocolStruct {
        address user;
        bytes32[] proof;
        address[] pools;
        uint256[] topUps;
    }

    // ========================= EVENTS =========================
    /// @notice Emit when a new merkleRoot is set & the fee is funded by the governance
    event SetMerkleRootAndFund(bytes32 indexed merkleRoot, uint256 amountFunded);

    /// @notice Emit when an user claims their fees
    event Claimed(address indexed user, uint256 amountOut);

    /// @notice Emit when the Pendle team populates data for a protocol
    event UpdateProtocolClaimable(address indexed user, uint256 sumTopUp);

    // ========================= FUNCTIONS =========================
    /**
     * @notice Submit total fee and proof to claim outstanding amount. Fee will be sent as raw ETH,
     *  so receiver should be an EOA or have receive() function.
     */
    function claimRetail(address receiver, uint256 totalAccrued, bytes32[] calldata proof)
        external
        returns (uint256 amountOut);

    /**
     * @notice Claim all outstanding fees for the specified pools. This function is intended for use
     * by protocols that have contacted the Pendle team. Note that the fee will be sent in raw ETH,
     * so the receiver should be an EOA or have a receive() function.
     * @notice Protocols should not use claimRetail, as it can make getProtocolFeeData unreliable.
     */
    function claimProtocol(address receiver, address[] calldata pools)
        external
        returns (uint256 totalAmountOut, uint256[] memory amountsOut);

    ///@notice Returns the claimable fees per pool. This function is only available if the Pendle
    ///team has specifically set up the data.
    function getProtocolClaimables(address user, address[] calldata pools)
        external
        view
        returns (uint256[] memory claimables);

    ///@notice Returns the lifetime totalAccrued fees for protocols. This function is only available
    ///if the Pendle team has specifically set up the data.
    function getProtocolTotalAccrued(address user) external view returns (uint256);

    function claimed(address user) external view returns (uint256);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol



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

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

// File: src/base/interfaces/IVePendle.sol



pragma solidity 0.8.7;

interface IVePendle {
    event BroadcastTotalSupply(VeBalance newTotalSupply, uint256[] chainIds);
    event BroadcastUserPosition(address indexed user, uint256[] chainIds);
    event Initialized(uint8 version);
    event NewLockPosition(address indexed user, uint128 amount, uint128 expiry);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event Withdraw(address indexed user, uint128 amount);

    struct Checkpoint {
        uint128 timestamp;
        VeBalance value;
    }

    struct VeBalance {
        uint128 bias;
        uint128 slope;
    }

    function MAX_LOCK_TIME() external view returns (uint128);

    function MIN_LOCK_TIME() external view returns (uint128);

    function WEEK() external view returns (uint128);

    function addDestinationContract(address _address, uint256 _chainId) external payable;

    function approxDstExecutionGas() external view returns (uint256);

    function balanceOf(address user) external view returns (uint128);

    function broadcastTotalSupply(uint256[] memory chainIds) external payable;

    function broadcastUserPosition(address user, uint256[] memory chainIds) external payable;

    function claimOwnership() external;

    function getAllDestinationContracts() external view returns (uint256[] memory chainIds, address[] memory addrs);

    function getBroadcastPositionFee(uint256[] memory chainIds) external view returns (uint256 fee);

    function getBroadcastSupplyFee(uint256[] memory chainIds) external view returns (uint256 fee);

    function getUserHistoryAt(address user, uint256 index) external view returns (Checkpoint memory);

    function getUserHistoryLength(address user) external view returns (uint256);

    function increaseLockPosition(uint128 additionalAmountToLock, uint128 newExpiry)
        external
        returns (uint128 newVeBalance);

    function increaseLockPositionAndBroadcast(
        uint128 additionalAmountToLock,
        uint128 newExpiry,
        uint256[] memory chainIds
    ) external payable returns (uint128 newVeBalance);

    function lastSlopeChangeAppliedAt() external view returns (uint128);

    function owner() external view returns (address);

    function pendingOwner() external view returns (address);

    function pendle() external view returns (address);

    function pendleMsgSendEndpoint() external view returns (address);

    function positionData(address) external view returns (uint128 amount, uint128 expiry);

    function setApproxDstExecutionGas(uint256 gas) external;

    function slopeChanges(uint128) external view returns (uint128);

    function totalSupplyAndBalanceCurrent(address user) external returns (uint128, uint128);

    function totalSupplyAt(uint128) external view returns (uint128);

    function totalSupplyCurrent() external returns (uint128);

    function totalSupplyStored() external view returns (uint128);

    function transferOwnership(address newOwner, bool direct, bool renounce) external;

    function withdraw() external returns (uint128 amount);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: lib/openzeppelin-contracts/contracts/utils/Address.sol



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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}