pragma solidity ^0.5.0;

contract UniswapExchangeInterface {
    // Address of ERC20 token sold on this exchange
    function tokenAddress() external view returns (address token);
    // Address of Uniswap Factory
    function factoryAddress() external view returns (address factory);
    // Provide Liquidity
    function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256);
    function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256);
    // Get Prices
    function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought);
    function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold);
    function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought);
    function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold);
    // Trade ETH to ERC20
    function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256  tokens_bought);
    function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256  tokens_bought);
    function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256  eth_sold);
    function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256  eth_sold);
    // Trade ERC20 to ETH
    function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256  eth_bought);
    function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient) external returns (uint256  eth_bought);
    function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256  tokens_sold);
    function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256  tokens_sold);
    // Trade ERC20 to ERC20
    function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256  tokens_bought);
    function tokenToTokenTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr) external returns (uint256  tokens_bought);
    function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256  tokens_sold);
    function tokenToTokenTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr) external returns (uint256  tokens_sold);
    // Trade ERC20 to Custom Pool
    function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256  tokens_bought);
    function tokenToExchangeTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address exchange_addr) external returns (uint256  tokens_bought);
    function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256  tokens_sold);
    function tokenToExchangeTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address exchange_addr) external returns (uint256  tokens_sold);
    // ERC20 comaptibility for liquidity tokens
    bytes32 public name;
    bytes32 public symbol;
    uint256 public decimals;
    function transfer(address _to, uint256 _value) external returns (bool);
    function transferFrom(address _from, address _to, uint256 value) external returns (bool);
    function approve(address _spender, uint256 _value) external returns (bool);
    function allowance(address _owner, address _spender) external view returns (uint256);
    function balanceOf(address _owner) external view returns (uint256);
    function totalSupply() external view returns (uint256);
    // Never use
    function setup(address token_addr) external;
}

contract WETH9Interface {
    function balanceOf(address who) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);

    function deposit() public payable;
    function withdraw(uint wad) public;

    function totalSupply() public view returns (uint);
    function approve(address guy, uint wad) public returns (bool);
    function transfer(address dst, uint wad) public returns (bool);
    function transferFrom(address src, address dst, uint wad) public returns (bool);
}

/**
 * @title Uniswap V1 ETH-WETH Exchange Liquidity Adder
 * @dev Help adding ETH to Uniswap ETH-WETH exchange in one tx.
 * @notice Do not send WETH or UNI token to this contract.
 */
contract UniswapWethLiquidityAdder {
    // Uniswap V1 ETH-WETH Exchange Address
    address public uniswapWethExchangeAddress = 0xA2881A90Bf33F03E7a3f803765Cd2ED5c8928dFb;
    address public wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    WETH9Interface weth = WETH9Interface(wethAddress);
    UniswapExchangeInterface uniswapWethExchange = UniswapExchangeInterface(uniswapWethExchangeAddress);

    constructor() public {
        // approve Uniswap ETH-WETH Exchange to transfer WETH from this contract
        weth.approve(uniswapWethExchangeAddress, 2**256 - 1);
    }

    function () external payable {
        addLiquidity();
    }

    // TODO: should this function return anything?
    /// @dev Receive ETH, add to Uniswap ETH-WETH exchange, and return UNI token.
    /// Will try to add all ETH in this contract to the liquidity pool.
    /// There may be WETH token stuck in this contract?, but we don't care
    function addLiquidity() public payable returns (uint256 liquidity) {
        // If no ETH is received, do nothing.
        if (msg.value == 0) {
            return 0;
        }

        // Get the amount of ETH now in this contract as the total amount of ETH we are going to add.
        uint256 totalEth = address(this).balance;

        // Compute the amount of ETH and WETH we will add.
        uint256 ethToAdd;
        uint256 wethToAdd;
        if (uniswapWethExchange.totalSupply() == 0) {
            // If no liquidity in the exchange, set ethToAdd:wethToAdd = 1:1.
            wethToAdd = totalEth / 2;
            ethToAdd = totalEth - wethToAdd;
        } else {
            // If there's liquidity in the exchange, set ethToAdd:wethToAdd = ethInPool:wethInPool.

            // Get the amount of ETH and WETH in the liquidity pool.
            uint256 ethInPool = uniswapWethExchangeAddress.balance;
            uint256 wethInPool = weth.balanceOf(uniswapWethExchangeAddress);

            // Calculate the amount of WETH we need to wrap.
            // We are solving this:
            //     Find maximum integer `ethToAdd` s.t.
            //     ethToAdd + wethToAdd <= totalEth
            //     wethToAdd = floor(ethToAdd * wethInPool / ethInPool) + 1
            // Solution:
            //     Let x = ethToAdd
            //         A = wethInPool
            //         B = ethInPool
            //         C = totalEth
            //     Then
            //         x + floor(x * A / B) + 1 <= C
            //         <=> x + x * A / B + 1 < C + 1
            //         <=> x + x * A / B < C
            //         <=> x < C * B / (A + B)
            //         <=> max int x = ceil(C * B / (A + B)) - 1
            //     So max ethToAdd = ceil(totalEth * ethInPool / (wethInPool + ethInPool)) - 1
            // Notes:
            //     1. In the following code, we set `ethToAdd` to `floor(C * B / (A + B)) - 1`
            //         instead of `ceil(C * B / (A + B)) - 1`
            //         because it's cheaper to compute,
            //         and the difference is at most 1 wei.
            //     2. We don't use SafeMath here because it's almost impossible to overflow
            //         when computing `ethBalance * ethBalance` or `ethBalance * wethBalance`.
            //         This saves some gas.
            ethToAdd = totalEth * ethInPool / (wethInPool + ethInPool) - 1;
            wethToAdd = ethToAdd * wethInPool / ethInPool + 1;
        }

        // Wrap ETH.
        weth.deposit.value(wethToAdd)();

        // Add liquidity.
        uint256 liquidityMinted = uniswapWethExchange.addLiquidity.value(ethToAdd)(1, 2**256-1, 2**256-1);

        // Transfer liquidity token to msg.sender.
        require(uniswapWethExchange.transfer(msg.sender, liquidityMinted));

        return liquidityMinted;
    }
}