// 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);
    }
}
[{"inputs":[{"internalType":"address","name":"_governance","type":"address"},{"internalType":"address","name":"_daoRecipient","type":"address"},{"internalType":"address","name":"_bountyRecipient","type":"address"},{"internalType":"address","name":"_veSdtFeeProxy","type":"address"},{"internalType":"address","name":"_votesRewardRecipient","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DIFFERENT_LENGTH","type":"error"},{"inputs":[],"name":"FEE_TOO_HIGH","type":"error"},{"inputs":[],"name":"NOT_ALLOWED","type":"error"},{"inputs":[],"name":"NOT_ALLOWED_TO_PULL","type":"error"},{"inputs":[],"name":"NOT_CLAIMED_ALL","type":"error"},{"inputs":[],"name":"NO_BALANCE","type":"error"},{"inputs":[],"name":"NO_REWARD","type":"error"},{"inputs":[],"name":"ONGOING_REWARD","type":"error"},{"inputs":[],"name":"WRONG_CLAIM","type":"error"},{"inputs":[],"name":"ZERO_ADDRESS","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_old","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_new","type":"uint256"}],"name":"BountyFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"BountyRecipientSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_old","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_new","type":"uint256"}],"name":"ClaimerFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_old","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_new","type":"uint256"}],"name":"DaoFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"DaoRecipientSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"_distributeAllRewards","type":"bool"}],"name":"DistributeVotersRewardsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"ERC20Rescued","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"GaugeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"GovernanceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"LockerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_old","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_new","type":"uint256"}],"name":"PeriodsToAddSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_gauge","type":"address"},{"indexed":false,"internalType":"address","name":"_tokenReward","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountNotified","type":"uint256"}],"name":"RewardNotified","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"SdtDistributorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_old","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_new","type":"uint256"}],"name":"VeSdtFeeProxyFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_old","type":"address"},{"indexed":false,"internalType":"address","name":"_new","type":"address"}],"name":"VeSdtFeeProxySet","type":"event"},{"inputs":[],"name":"PENDLE_FEE_D","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VE_PENDLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bountyFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bountyRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"canPullTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_pools","type":"address[]"}],"name":"claimForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimForVePendle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_pools","type":"address[]"}],"name":"claimForVoters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimerFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"daoFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"daoRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"distributeVotersRewards","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"notifyReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"notifyRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"periodsToAdd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"periodsToNotify","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokens","type":"address[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"}],"name":"pullTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"rescueToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sdtDistributor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_bountyFee","type":"uint256"}],"name":"setBountyFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_bountyRecipient","type":"address"}],"name":"setBountyRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_claimerFee","type":"uint256"}],"name":"setClaimerFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_daoFee","type":"uint256"}],"name":"setDaoFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_daoRecipient","type":"address"}],"name":"setDaoRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_distributeVotersRewards","type":"bool"}],"name":"setDistributeVotersRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"}],"name":"setGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_locker","type":"address"}],"name":"setLocker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_periodsToAdd","type":"uint256"}],"name":"setPeriodsToAdd","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_sdtDistributor","type":"address"}],"name":"setSdtDistributor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_veSdtFeeProxy","type":"address"}],"name":"setVeSdtFeeProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_veSdtFeeProxyFee","type":"uint256"}],"name":"setVeSdtFeeProxyFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"togglePullAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"veSdtFeeProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"veSdtFeeProxyFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"votesRewardRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]