contract RewardedWbtcPbtcUniV2Pair is IERC777Recipient, Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // prettier-ignore
    IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
    bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

    IERC20 public wbtc;
    IERC20 public pbtc;
    IUniswapV2Pair public uniV2;
    ModifiedUnipool public modifiedUnipool;

    uint256 public allowedSlippage;

    event Staked(address indexed user, uint256 amount);
    event Unstaked(address indexed user, uint256 amount);
    event AllowedSlippageChanged(uint256 slippage);

    /**
     * @param _uniV2 UniV2Pair address
     * @param _modifiedUnipool ModifiedUnipool address
     */
    constructor(address _uniV2, address _modifiedUnipool) public {
        require(Address.isContract(_uniV2), "RewardedWbtcPbtcUniV2Pair: _uniV2 not a contract");

        uniV2 = IUniswapV2Pair(_uniV2);
        modifiedUnipool = ModifiedUnipool(_modifiedUnipool);
        wbtc = IERC20(uniV2.token0());
        pbtc = IERC20(uniV2.token1());
        _erc1820.setInterfaceImplementer(
            address(this),
            TOKENS_RECIPIENT_INTERFACE_HASH,
            address(this)
        );
    }

    /**
     *  @param _allowedSlippage new max allowed
     */
    function setAllowedSlippage(uint256 _allowedSlippage) external onlyOwner {
        allowedSlippage = _allowedSlippage;
        emit AllowedSlippageChanged(_allowedSlippage);
    }

    /**
     * @notice ERC777 hook invoked when this contract receives a token.
     */
    function tokensReceived(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes calldata _userData,
        bytes calldata _operatorData
    ) external {
        // NOTE: avoid tx fail during uniV2.burn(address(this))
        if (_from == address(uniV2)) return;

        require(msg.sender == address(pbtc), "RewardedWbtcPbtcUniV2Pair: Invalid token");
        require(_amount > 0, "RewardedWbtcPbtcUniV2Pair: amount must be greater than 0");
        _stakeFor(_from, _amount);
    }

    /**
     * @notice Burn all user's UniV2 staked in the ModifiedUnipool,
     *         swap the corresponding amount of WBTC into PBTC, collect
     *         rewards matured from the UniV2 staking and sent it to msg.sender.
     *         User must approve this contract to to withdraw the corresponing
     *         amount of his UniV2 balance in behalf of him.
     */
    function unstake() public returns (bool) {
        uint256 uniV2SenderBalance = modifiedUnipool.balanceOf(msg.sender);
        require(
            modifiedUnipool.allowance(msg.sender, address(this)) >= uniV2SenderBalance,
            "RewardedWbtcPbtcUniV2Pair: amount not approved"
        );

        modifiedUnipool.withdrawFrom(msg.sender, uniV2SenderBalance);
        modifiedUnipool.getReward(msg.sender);

        uniV2.transfer(address(uniV2), uniV2SenderBalance);
        (uint256 wbtcAmount, uint256 pbtcAmount) = uniV2.burn(address(this));

        (uint256 wbtcReserve, uint256 pbtcReserve, ) = uniV2.getReserves();
        uint256 pbtcAmountOut = UniswapV2Library.getAmountOut(wbtcAmount, wbtcReserve, pbtcReserve);

        wbtc.safeTransfer(address(uniV2), wbtcAmount);
        uniV2.swap(0, pbtcAmountOut, address(this), "");

        uint256 pbtcTotalAmount = pbtcAmount + pbtcAmountOut;
        pbtc.safeTransfer(msg.sender, pbtcTotalAmount);

        emit Unstaked(msg.sender, pbtcTotalAmount);
        return true;
    }

    /**
     * @notice Swap _amount / 2 pBTC sent to this contract
     *         into WBTC and put them into a pBTC/WBTC Uniswap pool.
     *         The amount of UniV2 token will be sent to ModifiedUnipool
     *         in order to mature reward.
     * @param _user address of the user who will have UniV2 tokens in ModifiedUnipool
     * @param _amount amount of pbtc to use to perform this operation
     */
    function _stakeFor(address _user, uint256 _amount) internal {
        uint256 pbtcAmountIn = _amount / 2;
        (uint256 wbtcReserve, uint256 pbtcReserve, ) = uniV2.getReserves();

        uint256 wbtcAmountOut = UniswapV2Library.getAmountOut(
            pbtcAmountIn,
            pbtcReserve,
            wbtcReserve
        );

        require(
            wbtcAmountOut >=
                UniswapV2Library.calculateSlippageAmountWithFees(
                    pbtcAmountIn,
                    allowedSlippage,
                    10**18,
                    10**8
                ),
            "RewardedWbtcPbtcUniV2Pair: too much slippage"
        );

        pbtc.safeTransfer(address(uniV2), pbtcAmountIn);
        uniV2.swap(wbtcAmountOut, 0, address(this), "");

        pbtc.safeTransfer(address(uniV2), pbtcAmountIn);
        wbtc.safeTransfer(address(uniV2), wbtcAmountOut);
        uint256 liquidity = uniV2.mint(address(this));

        uniV2.approve(address(modifiedUnipool), liquidity);
        modifiedUnipool.stakeFor(_user, liquidity);

        emit Staked(_user, _amount);
    }
}
function unstake() public returns (bool) {
    uint256 uniV2SenderBalance = modifiedUnipool.balanceOf(msg.sender);
    require(
        modifiedUnipool.allowance(msg.sender, address(this)) >= uniV2SenderBalance,
        "RewardedWbtcPbtcUniV2Pair: amount not approved"
    );

    modifiedUnipool.withdrawFrom(msg.sender, uniV2SenderBalance);
    modifiedUnipool.getReward(msg.sender);

    uniV2.transfer(address(uniV2), uniV2SenderBalance);
    (uint256 wbtcAmount, uint256 pbtcAmount) = uniV2.burn(address(this));

    (uint256 wbtcReserve, uint256 pbtcReserve, ) = uniV2.getReserves();
    uint256 pbtcAmountOut = UniswapV2Library.getAmountOut(wbtcAmount, wbtcReserve, pbtcReserve);

    wbtc.safeTransfer(address(uniV2), wbtcAmount);
    uniV2.swap(0, pbtcAmountOut, address(this), "");

    uint256 pbtcTotalAmount = pbtcAmount + pbtcAmountOut;
    pbtc.safeTransfer(msg.sender, pbtcTotalAmount);

    emit Unstaked(msg.sender, pbtcTotalAmount);
    return true;
}

/**
 * @notice Burn all user's UniV2 staked in the ModifiedUnipool,
 *         swap the corresponding amount of WBTC into PBTC, collect
 *         rewards matured from the UniV2 staking and sent it to msg.sender.
 *         User must approve this contract to to withdraw the corresponing
 *         amount of his UniV2 balance in behalf of him.
 */