// SPDX-License-Identifier: MIT
// solium-disable security/no-low-level-calls
pragma solidity ^0.6.12;

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

interface IUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Pair {
    function totalSupply() external view returns (uint);
    function transferFrom(address from, address to, uint value) external returns (bool);
    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function PERMIT_TYPEHASH() external pure returns (bytes32);
    function nonces(address owner) external view returns (uint);
    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function mint(address to) external returns (uint liquidity);
    function burn(address to) external returns (uint amount0, uint amount1);
    function skim(address to) external;
}

interface IMasterChef {
    function deposit(uint256 _pid, uint256 _amount) external;
}

contract Ownable {
    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor () internal {
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    function renounceOwnership() public virtual {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        emit OwnershipTransferred(owner, address(0));
        owner = address(0);
    }

    function transferOwnership(address newOwner) public virtual {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    modifier onlyOwner() {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }
}

contract UniV2toSushiMigrator is Ownable {
    uint256 public migrateRefund = 0; // 50000000? SUSHI refund per gwei in gasprice
    uint256 public newPairRefund = 0; // 400000000? SUSHI refund per gwei in gasprice
    uint256 public maxGasPrice = 100000000000; // Max gas price to limit exploits

    function set(uint256 migrateRefund_, uint256 newPairRefund_, uint256 maxGasPrice_) public onlyOwner {
        migrateRefund = migrateRefund_;
        newPairRefund = newPairRefund_;
        maxGasPrice = maxGasPrice_;
    }
    
    function drain(address token) public onlyOwner {
        safeTransfer(token, msg.sender, IERC20(token).balanceOf(address(this)));
    }
    
    function min(uint x, uint y) internal pure returns (uint z) {
        z = x < y ? x : y;
    }

    function safeTransfer(address token, address to, uint256 amount) private {
        if (amount > 0) {
            (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0xa9059cbb, to, amount));
            require(success && (data.length == 0 || abi.decode(data, (bool))), "Transfer failed at ERC20");
        }
    }
    
    function reward(uint256 amount) private {
        if (amount > 0) {
             // Just try to reward SUSHI, if it fails, continue
            address(0x6B3595068778DD592e39A122f4f5a5cF09C90fE2).call(abi.encodeWithSelector(0xa9059cbb, msg.sender, min(tx.gasprice, maxGasPrice) * amount));
        }
    }
    
    function migrate(IUniswapV2Pair uniPair, uint liquidity) public returns(uint256 newLiquidity) {
        uniPair.transferFrom(msg.sender, address(uniPair), liquidity); // send liquidity to pair

        address token0 = uniPair.token0();
        address token1 = uniPair.token1();
        address sushiPair = IUniswapV2Factory(0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac).getPair(token0, token1);
        if (sushiPair == address(0)) { // create the pair if it doesn't exist yet
            reward(newPairRefund);

            sushiPair = IUniswapV2Factory(0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac).createPair(token0, token1);
            uniPair.burn(sushiPair); // Remove liquidity directly to sushiPair
        } else {
            reward(migrateRefund);
    
            (uint256 amount0, uint256 amount1) = uniPair.burn(address(this)); // Remove liquidity to here
            (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(sushiPair).getReserves();
            uint totalSupply = IUniswapV2Pair(sushiPair).totalSupply();
            uint256 liquidity0 = amount0 * totalSupply / reserve0;
            uint256 liquidity1 = amount1 * totalSupply / reserve1;
    
            if (liquidity0 == liquidity1) {
                safeTransfer(token0, sushiPair, amount0);
                safeTransfer(token1, sushiPair, amount1);
            } else if (liquidity0 < liquidity1) { // There is too much of token1
                uint256 adjustedAmount1 = amount1 * liquidity0 / liquidity1;
                safeTransfer(token0, sushiPair, amount0);
                safeTransfer(token1, sushiPair, adjustedAmount1);
                safeTransfer(token1, msg.sender, amount1 - adjustedAmount1);
            } else { // There is too much of token0
                uint256 adjustedAmount0 = amount0 * liquidity1 / liquidity0;
                safeTransfer(token0, sushiPair, adjustedAmount0);
                safeTransfer(token1, sushiPair, amount1);
                safeTransfer(token0, msg.sender, amount0 - adjustedAmount0);
            }
        }
        
        return IUniswapV2Pair(sushiPair).mint(msg.sender); // Add liquidity
    }

    function permitAndMigrate(IUniswapV2Pair uniPair, uint liquidity, uint deadline, uint8 v, bytes32 r, bytes32 s) public returns(uint256 newLiquidity) {
        uniPair.permit(msg.sender, address(this), liquidity, deadline, v, r, s);
        return migrate(uniPair, liquidity);
    }
}