diff --git a/contracts/UniswapERC20.sol b/contracts/UniswapERC20.sol index df14d1a..ca53af9 100644 --- a/contracts/UniswapERC20.sol +++ b/contracts/UniswapERC20.sol @@ -1,8 +1,6 @@ pragma solidity ^0.5.11; import './ERC20.sol'; import './interfaces/IERC20.sol'; -import './interfaces/IUniswapERC20Factory.sol'; - contract UniswapERC20 is ERC20 { @@ -14,9 +12,9 @@ contract UniswapERC20 is ERC20 { string public name; // Uniswap V2 string public symbol; // UNI-V2 uint256 public decimals; // 18 - IERC20 tokenA; // ERC20 token traded on this contract - IERC20 tokenB; // ERC20 token traded on this contract - IUniswapFactory factory; // factory that created this contract + address public tokenA; // ERC20 token traded on this contract + address public tokenB; // ERC20 token traded on this contract + address public factoryAddress; // factory that created this contract bool private rentrancyLock = false; @@ -30,9 +28,9 @@ contract UniswapERC20 is ERC20 { constructor(address _tokenA, address _tokenB) public { require(address(_tokenA) != address(0) && _tokenB != address(0), 'INVALID_ADDRESS'); - factory = IUniswapFactory(msg.sender); - tokenA = IERC20(_tokenA); - tokenB = IERC20(_tokenB); + factoryAddress = msg.sender; + tokenA = _tokenA; + tokenB = _tokenB; name = 'Uniswap V2'; symbol = 'UNI-V2'; decimals = 18; @@ -61,185 +59,154 @@ contract UniswapERC20 is ERC20 { return (numerator / denominator).add(1); } - function ethToTokenInput(uint256 ethSold, uint256 minTokens, uint256 deadline, address buyer, address recipient) private nonReentrant returns (uint256) { - require(deadline >= block.timestamp && ethSold > 0 && minTokens > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 tokensBought = getInputPrice(ethSold, address(this).balance.sub(ethSold), tokenReserve); - require(tokensBought >= minTokens); - require(token.transfer(recipient, tokensBought)); - emit TokenPurchase(buyer, ethSold, tokensBought); - return tokensBought; + + //TO: DO msg.sender is wrapper + function swapInput(address inputToken, uint256 amountSold, address recipient) public nonReentrant returns (uint256) { + address _tokenA = address(tokenA); + address _tokenB = address(tokenB); + bool inputIsA = inputToken == _tokenA; + require(inputIsA || inputToken == _tokenB); + address outputToken = _tokenA; + if(inputIsA) { + outputToken == _tokenB; + } + + uint256 inputReserve = IERC20(inputToken).balanceOf(address(this)); + uint256 outputReserve = IERC20(outputToken).balanceOf(address(this)); + uint256 amountBought = getInputPrice(amountSold, inputReserve, outputReserve); + require(IERC20(inputToken).transferFrom(msg.sender, address(this), amountSold)); + require(IERC20(outputToken).transfer(recipient, amountBought)); + + if(inputIsA) { + emit SwapAForB(msg.sender, amountSold, amountBought); + } else { + emit SwapBForA(msg.sender, amountSold, amountBought); + } + + return amountBought; } - function ethToTokenSwapInput(uint256 minTokens, uint256 deadline) public payable returns (uint256) { - return ethToTokenInput(msg.value, minTokens, deadline, msg.sender, msg.sender); + //TO: DO msg.sender is wrapper + function swapOutput(address outputToken, uint256 amountBought, address recipient) public nonReentrant returns (uint256) { + address _tokenA = address(tokenA); + address _tokenB = address(tokenB); + bool outputIsA = outputToken == _tokenA; + require(outputIsA || outputToken == _tokenB); + address inputToken = _tokenA; + if(outputIsA) { + inputToken == _tokenB; + } + + uint256 inputReserve = IERC20(inputToken).balanceOf(address(this)); + uint256 outputReserve = IERC20(outputToken).balanceOf(address(this)); + uint256 amountSold = getOutputPrice(amountBought, inputReserve, outputReserve); + require(IERC20(inputToken).transferFrom(msg.sender, address(this), amountSold)); + require(IERC20(outputToken).transfer(recipient, amountBought)); + + if(outputIsA) { + emit SwapBForA(msg.sender, amountSold, amountBought); + } else { + emit SwapAForB(msg.sender, amountSold, amountBought); + } + + return amountSold; } - function ethToTokenTransferInput(uint256 minTokens, uint256 deadline, address recipient) public payable returns(uint256) { - require(recipient != address(this) && recipient != address(0)); - return ethToTokenInput(msg.value, minTokens, deadline, msg.sender, recipient); - } - - function ethToTokenOutput(uint256 tokensBought, uint256 maxEth, uint256 deadline, address payable buyer, address recipient) private nonReentrant returns (uint256) { - require(deadline >= block.timestamp && tokensBought > 0 && maxEth > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 ethSold = getOutputPrice(tokensBought, address(this).balance.sub(maxEth), tokenReserve); - // Throws if ethSold > maxEth - uint256 ethRefund = maxEth.sub(ethSold); - if (ethRefund > 0) { - buyer.transfer(ethRefund); + function getInputPrice(address inputToken, uint256 amountSold) public view returns (uint256) { + require(amountSold > 0); + address _tokenA = address(tokenA); + address _tokenB = address(tokenB); + require(inputToken == _tokenA || inputToken == _tokenB); + address outputToken = _tokenA; + if(inputToken == _tokenA) { + outputToken = _tokenB; } - require(token.transfer(recipient, tokensBought)); - emit TokenPurchase(buyer, ethSold, tokensBought); - return ethSold; + uint256 inputReserve = IERC20(inputToken).balanceOf(address(this)); + uint256 outputReserve = IERC20(outputToken).balanceOf(address(this)); + return getInputPrice(amountSold, inputReserve, outputReserve); } - function ethToTokenSwapOutput(uint256 tokensBought, uint256 deadline) public payable returns(uint256) { - return ethToTokenOutput(tokensBought, msg.value, deadline, msg.sender, msg.sender); + function getOutputPrice(address outputToken, uint256 amountBought) public view returns (uint256) { + require(amountBought > 0); + address _tokenA = address(tokenA); + address _tokenB = address(tokenB); + require(outputToken == _tokenA || outputToken == _tokenB); + address inputToken = _tokenA; + if(outputToken == _tokenA) { + inputToken = _tokenB; + } + uint256 inputReserve = IERC20(inputToken).balanceOf(address(this)); + uint256 outputReserve = IERC20(outputToken).balanceOf(address(this)); + return getOutputPrice(amountBought, inputReserve, outputReserve); } - function ethToTokenTransferOutput(uint256 tokensBought, uint256 deadline, address recipient) public payable returns (uint256) { - require(recipient != address(this) && recipient != address(0)); - return ethToTokenOutput(tokensBought, msg.value, deadline, msg.sender, recipient); - } - - function tokenToEthInput(uint256 tokensSold, uint256 minEth, uint256 deadline, address buyer, address payable recipient) private nonReentrant returns (uint256) { - require(deadline >= block.timestamp && tokensSold > 0 && minEth > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 ethBought = getInputPrice(tokensSold, tokenReserve, address(this).balance); - require(ethBought >= minEth); - recipient.transfer(ethBought); - require(token.transferFrom(buyer, address(this), tokensSold)); - emit EthPurchase(buyer, tokensSold, ethBought); - return ethBought; + function tokenAAddress() public view returns (address) { + return address(tokenA); } - function tokenToEthSwapInput(uint256 tokensSold, uint256 minEth, uint256 deadline) public returns (uint256) { - return tokenToEthInput(tokensSold, minEth, deadline, msg.sender, msg.sender); + function tokenBAddress() public view returns (address) { + return address(tokenB); } - function tokenToEthTransferInput(uint256 tokensSold, uint256 minEth, uint256 deadline, address payable recipient) public returns (uint256) { - require(recipient != address(this) && recipient != address(0)); - return tokenToEthInput(tokensSold, minEth, deadline, msg.sender, recipient); - } + function addLiquidity(uint256 amountA, uint256 maxTokenB, uint256 minLiquidity) public nonReentrant returns (uint256) { + require(amountA > 0 && maxTokenB > 0); + uint256 _totalSupply = totalSupply; + address _tokenA = tokenA; + address _tokenB = tokenB; - function tokenToEthOutput(uint256 ethBought, uint256 maxTokens, uint256 deadline, address buyer, address payable recipient) private nonReentrant returns (uint256) { - require(deadline >= block.timestamp && ethBought > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 tokensSold = getOutputPrice(ethBought, tokenReserve, address(this).balance); - // tokens sold is always > 0 - require(maxTokens >= tokensSold); - recipient.transfer(ethBought); - require(token.transferFrom(buyer, address(this), tokensSold)); - emit EthPurchase(buyer, tokensSold, ethBought); - return tokensSold; - } - - - function tokenToEthSwapOutput(uint256 ethBought, uint256 maxTokens, uint256 deadline) public returns (uint256) { - return tokenToEthOutput(ethBought, maxTokens, deadline, msg.sender, msg.sender); - } - - - function tokenToEthTransferOutput(uint256 ethBought, uint256 maxTokens, uint256 deadline, address payable recipient) public returns (uint256) { - require(recipient != address(this) && recipient != address(0)); - return tokenToEthOutput(ethBought, maxTokens, deadline, msg.sender, recipient); - } - - - function getEthToTokenInputPrice(uint256 ethSold) public view returns (uint256) { - require(ethSold > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - return getInputPrice(ethSold, address(this).balance, tokenReserve); - } - - - function getEthToTokenOutputPrice(uint256 tokensBought) public view returns (uint256) { - require(tokensBought > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 ethSold = getOutputPrice(tokensBought, address(this).balance, tokenReserve); - return ethSold; - } - - - function getTokenToEthInputPrice(uint256 tokensSold) public view returns (uint256) { - require(tokensSold > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 ethBought = getInputPrice(tokensSold, tokenReserve, address(this).balance); - return ethBought; - } - - - function getTokenToEthOutputPrice(uint256 ethBought) public view returns (uint256) { - require(ethBought > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - return getOutputPrice(ethBought, tokenReserve, address(this).balance); - } - - - function tokenAddress() public view returns (address) { - return address(token); - } - - - function factoryAddress() public view returns (address) { - return address(factory); - } - - - function addLiquidity(uint256 minLiquidity, uint256 maxTokens, uint256 deadline) public payable nonReentrant returns (uint256) { - require(deadline >= block.timestamp && maxTokens > 0 && msg.value > 0, 'INVALID_INPUT'); - uint256 totalLiquidity = totalSupply; - - if (totalLiquidity > 0) { + if (_totalSupply > 0) { require(minLiquidity > 0); - uint256 ethReserve = address(this).balance.sub(msg.value); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 tokenAmount = (msg.value.mul(tokenReserve) / ethReserve).add(1); - uint256 liquidityMinted = msg.value.mul(totalLiquidity) / ethReserve; - require(maxTokens >= tokenAmount && liquidityMinted >= minLiquidity); + + uint256 reserveA = IERC20(_tokenA).balanceOf(address(this)); + uint256 reserveB = IERC20(_tokenB).balanceOf(address(this)); + uint256 amountB = (amountA.mul(reserveB) / reserveA).add(1); + uint256 liquidityMinted = amountA.mul(_totalSupply) / reserveA; + require(maxTokenB >= amountB && liquidityMinted >= minLiquidity); balanceOf[msg.sender] = balanceOf[msg.sender].add(liquidityMinted); - totalSupply = totalLiquidity.add(liquidityMinted); - require(token.transferFrom(msg.sender, address(this), tokenAmount)); - emit AddLiquidity(msg.sender, msg.value, tokenAmount); + totalSupply = _totalSupply.add(liquidityMinted); + require(IERC20(_tokenA).transferFrom(msg.sender, address(this), amountA)); + require(IERC20(_tokenB).transferFrom(msg.sender, address(this), amountB)); + emit AddLiquidity(msg.sender, amountA, amountB); emit Transfer(address(0), msg.sender, liquidityMinted); return liquidityMinted; } else { - require(msg.value >= 1000000000, 'INVALID_VALUE'); - require(factory.getExchange(address(token)) == address(this)); - uint256 tokenAmount = maxTokens; - uint256 initialLiquidity = address(this).balance; + // TODO: figure out how to set this safely + // arithemtic or geometric mean? + uint256 initialLiquidity = amountA; totalSupply = initialLiquidity; balanceOf[msg.sender] = initialLiquidity; - require(token.transferFrom(msg.sender, address(this), tokenAmount)); - emit AddLiquidity(msg.sender, msg.value, tokenAmount); + require(IERC20(_tokenA).transferFrom(msg.sender, address(this), amountA)); + require(IERC20(_tokenB).transferFrom(msg.sender, address(this), maxTokenB)); + emit AddLiquidity(msg.sender, amountA, maxTokenB); emit Transfer(address(0), msg.sender, initialLiquidity); return initialLiquidity; } } - function removeLiquidity(uint256 amount, uint256 minEth, uint256 minTokens, uint256 deadline) public nonReentrant returns (uint256, uint256) { - require(amount > 0 && deadline >= block.timestamp && minEth > 0 && minTokens > 0); - uint256 totalLiquidity = totalSupply; - require(totalLiquidity > 0); - uint256 tokenReserve = token.balanceOf(address(this)); - uint256 ethAmount = amount.mul(address(this).balance) / totalLiquidity; - uint256 tokenAmount = amount.mul(tokenReserve) / totalLiquidity; - require(ethAmount >= minEth && tokenAmount >= minTokens); + function removeLiquidity(uint256 amount, uint256 minTokenA, uint256 minTokenB) public nonReentrant returns (uint256, uint256) { + uint256 _totalSupply = totalSupply; + require(amount > 0 && minTokenA > 0 && minTokenB > 0 && _totalSupply > 0); + address _tokenA = tokenA; + address _tokenB = tokenB; + uint256 reserveA = IERC20(_tokenA).balanceOf(address(this)); + uint256 reserveB = IERC20(_tokenB).balanceOf(address(this)); + uint256 tokenAAmount = amount.mul(reserveA) / _totalSupply; + uint256 tokenBAmount = amount.mul(reserveB) / _totalSupply; + require(tokenAAmount >= minTokenA && tokenBAmount >= minTokenB); balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount); - totalSupply = totalLiquidity.sub(amount); - msg.sender.transfer(ethAmount); - require(token.transfer(msg.sender, tokenAmount)); - emit RemoveLiquidity(msg.sender, ethAmount, tokenAmount); + totalSupply = _totalSupply.sub(amount); + require(IERC20(_tokenA).transfer(msg.sender, tokenAAmount)); + require(IERC20(_tokenB).transfer(msg.sender, tokenBAmount)); + emit RemoveLiquidity(msg.sender, tokenAAmount, tokenBAmount); emit Transfer(msg.sender, address(0), amount); - return (ethAmount, tokenAmount); + return (tokenAAmount, tokenBAmount); } } diff --git a/contracts/UniswapERC20Factory.sol b/contracts/UniswapERC20Factory.sol index ade518f..69711e3 100644 --- a/contracts/UniswapERC20Factory.sol +++ b/contracts/UniswapERC20Factory.sol @@ -20,11 +20,11 @@ contract UniswapERC20Factory { require(token1 != address(0) && token2 != address(0) && token1 != token2); require(setExchange[token1][token2] == address(0), 'EXCHANGE_EXISTS'); - address tokenA = token1 - address tokenB = token2 + address tokenA = token1; + address tokenB = token2; if(uint256(token2) < uint256(token1)) { - tokenA = token2 - tokenB = token1 + tokenA = token2; + tokenB = token1; } UniswapERC20 exchange = new UniswapERC20(tokenA, tokenB); @@ -34,7 +34,7 @@ contract UniswapERC20Factory { uint256 exchangeId = exchangeCount + 1; exchangeCount = exchangeId; - getExchangeWithId[exchangeId] = exchangeId; + getExchangeWithId[exchangeId] = address(exchange); emit NewERC20Exchange(tokenA, tokenB, address(exchange)); return address(exchange);