From 59d19b296294d6f8bb1878aa5ad13efcb8ccc048 Mon Sep 17 00:00:00 2001 From: haydenadams Date: Thu, 29 Aug 2019 16:10:44 -0400 Subject: [PATCH] finish initial contracts --- contracts/ERC20.sol | 45 +++ contracts/SafeMath.sol | 40 +++ contracts/UniswapExchange.sol | 344 ++++++++++++++++++++++ contracts/UniswapFactory.sol | 28 ++ contracts/interfaces/IERC20.sol | 16 + contracts/interfaces/IUniswapExchange.sol | 254 ++++++++++++++++ contracts/interfaces/IUniswapFactory.sol | 11 + contracts/test_contracts/TestToken.sol | 56 ++++ contracts/test_contracts/Token.sol | 15 - test/testExchange.js | 0 test/testFactory.js | 0 11 files changed, 794 insertions(+), 15 deletions(-) create mode 100644 contracts/ERC20.sol create mode 100644 contracts/SafeMath.sol create mode 100644 contracts/UniswapExchange.sol create mode 100644 contracts/UniswapFactory.sol create mode 100644 contracts/interfaces/IERC20.sol create mode 100644 contracts/interfaces/IUniswapExchange.sol create mode 100644 contracts/interfaces/IUniswapFactory.sol create mode 100644 contracts/test_contracts/TestToken.sol delete mode 100644 contracts/test_contracts/Token.sol create mode 100644 test/testExchange.js create mode 100644 test/testFactory.js diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol new file mode 100644 index 000000000..fc79bd6a6 --- /dev/null +++ b/contracts/ERC20.sol @@ -0,0 +1,45 @@ +pragma solidity ^0.5.11; +import "./SafeMath.sol"; + + +contract ERC20 { + using SafeMath for uint256; + + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + uint256 public totalSupply; + uint256 internal constant MAX_UINT256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + function transfer(address to, uint256 value) public returns (bool) { + balanceOf[msg.sender] = balanceOf[msg.sender].sub(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(msg.sender, to, value); + return true; + } + + function transferFrom(address from, address to, uint256 value) public returns (bool) { + if (allowance[from][msg.sender] < MAX_UINT256) { + allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); + } + balanceOf[from] = balanceOf[from].sub(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(from, to, value); + return true; + } + + function approve(address spender, uint256 value) public returns (bool) { + allowance[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + function burn(uint256 value) public { + totalSupply = totalSupply.sub(value); + balanceOf[msg.sender] = balanceOf[msg.sender].sub(value); + emit Transfer(msg.sender, address(0), value); + } +} diff --git a/contracts/SafeMath.sol b/contracts/SafeMath.sol new file mode 100644 index 000000000..912876a45 --- /dev/null +++ b/contracts/SafeMath.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.5.11; + +library SafeMath { + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + uint256 c = a - b; + return c; + } + + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, "SafeMath: division by zero"); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + +} diff --git a/contracts/UniswapExchange.sol b/contracts/UniswapExchange.sol new file mode 100644 index 000000000..7792ff57b --- /dev/null +++ b/contracts/UniswapExchange.sol @@ -0,0 +1,344 @@ +pragma solidity ^0.5.11; +import './ERC20.sol'; +import './interfaces/IERC20.sol'; +import './interfaces/IUniswapFactory.sol'; +import './interfaces/IUniswapExchange.sol'; + + +contract UniswapExchange is ERC20 { + + event TokenPurchase(address indexed buyer, uint256 indexed ethSold, uint256 indexed tokensBought); + event EthPurchase(address indexed buyer, uint256 indexed tokensSold, uint256 indexed ethBought); + event AddLiquidity(address indexed provider, uint256 indexed ethAmount, uint256 indexed tokenAmount); + event RemoveLiquidity(address indexed provider, uint256 indexed ethAmount, uint256 indexed tokenAmount); + + string public name; // Uniswap V1 + string public symbol; // UNI-V1 + uint256 public decimals; // 18 + IERC20 token; // address of the ERC20 token traded on this contract + IUniswapFactory factory; // interface for the factory that created this contract + + bool private rentrancyLock = false; + + modifier nonReentrant() { + require(!rentrancyLock); + rentrancyLock = true; + _; + rentrancyLock = false; + } + + + constructor(address tokenAddr) public { + require( + address(factory) == address(0) && address(token) == address(0) && tokenAddr != address(0), + 'INVALID_ADDRESS' + ); + factory = IUniswapFactory(msg.sender); + token = IERC20(tokenAddr); + name = 'Uniswap V2'; + symbol = 'UNI-V2'; + decimals = 18; + } + + + function () external payable { + ethToTokenInput(msg.value, 1, block.timestamp, msg.sender, msg.sender); + } + + + function getInputPrice(uint256 inputAmount, uint256 inputReserve, uint256 outputReserve) public pure returns (uint256) { + require(inputReserve > 0 && outputReserve > 0, 'INVALID_VALUE'); + uint256 inputAmountWithFee = inputAmount.mul(997); + uint256 numerator = inputAmountWithFee.mul(outputReserve); + uint256 denominator = inputReserve.mul(1000).add(inputAmountWithFee); + return numerator / denominator; + } + + + function getOutputPrice(uint256 outputAmount, uint256 inputReserve, uint256 outputReserve) public pure returns (uint256) { + require(inputReserve > 0 && outputReserve > 0); + uint256 numerator = inputReserve.mul(outputAmount).mul(1000); + uint256 denominator = (outputReserve.sub(outputAmount)).mul(997); + 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; + } + + + function ethToTokenSwapInput(uint256 minTokens, uint256 deadline) public payable returns (uint256) { + return ethToTokenInput(msg.value, minTokens, deadline, msg.sender, msg.sender); + } + + + 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); + } + require(token.transfer(recipient, tokensBought)); + emit TokenPurchase(buyer, ethSold, tokensBought); + return ethSold; + } + + + function ethToTokenSwapOutput(uint256 tokensBought, uint256 deadline) public payable returns(uint256) { + return ethToTokenOutput(tokensBought, msg.value, deadline, msg.sender, msg.sender); + } + + + 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 tokenToEthSwapInput(uint256 tokensSold, uint256 minEth, uint256 deadline) public returns (uint256) { + return tokenToEthInput(tokensSold, minEth, deadline, msg.sender, msg.sender); + } + + + 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 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 tokenToTokenInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256 minEthBought, + uint256 deadline, + address buyer, + address recipient, + address payable exchangeAddr) + private nonReentrant returns (uint256) + { + require(deadline >= block.timestamp && tokensSold > 0 && minTokensBought > 0 && minEthBought > 0); + require(exchangeAddr != address(this) && exchangeAddr != address(0)); + uint256 tokenReserve = token.balanceOf(address(this)); + uint256 ethBought = getInputPrice(tokensSold, tokenReserve, address(this).balance); + require(ethBought >= minEthBought); + require(token.transferFrom(buyer, address(this), tokensSold)); + uint256 tokensBought = IUniswapExchange(exchangeAddr).ethToTokenTransferInput.value(ethBought)(minTokensBought, deadline, recipient); + emit EthPurchase(buyer, tokensSold, ethBought); + return tokensBought; + } + + + function tokenToTokenSwapInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256 minEthBought, + uint256 deadline, + address tokenAddr) + public returns (uint256) + { + address payable exchangeAddr = factory.getExchange(tokenAddr); + return tokenToTokenInput(tokensSold, minTokensBought, minEthBought, deadline, msg.sender, msg.sender, exchangeAddr); + } + + + function tokenToTokenTransferInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256 minEthBought, + uint256 deadline, + address recipient, + address tokenAddr) + public returns (uint256) + { + address payable exchangeAddr = factory.getExchange(tokenAddr); + return tokenToTokenInput(tokensSold, minTokensBought, minEthBought, deadline, msg.sender, recipient, exchangeAddr); + } + + function tokenToTokenOutput( + uint256 tokensBought, + uint256 maxTokensSold, + uint256 maxEthSold, + uint256 deadline, + address buyer, + address recipient, + address payable exchangeAddr) + private nonReentrant returns (uint256) + { + require(deadline >= block.timestamp && (tokensBought > 0 && maxEthSold > 0)); + require(exchangeAddr != address(this) && exchangeAddr != address(0)); + uint256 ethBought = IUniswapExchange(exchangeAddr).getEthToTokenOutputPrice(tokensBought); + uint256 tokenReserve = token.balanceOf(address(this)); + uint256 tokensSold = getOutputPrice(ethBought, tokenReserve, address(this).balance); + // tokens sold is always > 0 + require(maxTokensSold >= tokensSold && maxEthSold >= ethBought); + require(token.transferFrom(buyer, address(this), tokensSold)); + IUniswapExchange(exchangeAddr).ethToTokenTransferOutput.value(ethBought)(tokensBought, deadline, recipient); + emit EthPurchase(buyer, tokensSold, ethBought); + return tokensSold; + } + + + function tokenToTokenSwapOutput( + uint256 tokensBought, + uint256 maxTokensSold, + uint256 maxEthSold, + uint256 deadline, + address tokenAddr) + public returns (uint256) + { + address payable exchangeAddr = factory.getExchange(tokenAddr); + return tokenToTokenOutput(tokensBought, maxTokensSold, maxEthSold, deadline, msg.sender, msg.sender, exchangeAddr); + } + + + function tokenToTokenTransferOutput( + uint256 tokensBought, + uint256 maxTokensSold, + uint256 maxEthSold, + uint256 deadline, + address recipient, + address tokenAddr) + public returns (uint256) + { + address payable exchangeAddr = factory.getExchange(tokenAddr); + return tokenToTokenOutput(tokensBought, maxTokensSold, maxEthSold, deadline, msg.sender, recipient, exchangeAddr); + } + + + 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, 'UniswapExchange#addLiquidity: INVALID_ARGUMENT'); + uint256 totalLiquidity = totalSupply; + + if (totalLiquidity > 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); + 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); + 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; + totalSupply = initialLiquidity; + balanceOf[msg.sender] = initialLiquidity; + require(token.transferFrom(msg.sender, address(this), tokenAmount)); + emit AddLiquidity(msg.sender, msg.value, tokenAmount); + 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); + 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); + emit Transfer(msg.sender, address(0), amount); + return (ethAmount, tokenAmount); + } +} diff --git a/contracts/UniswapFactory.sol b/contracts/UniswapFactory.sol new file mode 100644 index 000000000..f2cf278a1 --- /dev/null +++ b/contracts/UniswapFactory.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.5.11; +import "./UniswapExchange.sol"; +import "./interfaces/IUniswapExchange.sol"; + + +contract UniswapFactory { + + event NewExchange(address indexed token, address indexed exchange); + + uint256 public tokenCount; + mapping (address => address) public getExchange; + mapping (address => address) internal getToken; + mapping (uint256 => address) internal getTokenWithId; + + function createExchange(address token) public returns (address) { + require(token != address(0)); + require(getExchange[token] == address(0), 'EXCHANGE_EXISTS'); + UniswapExchange exchange = new UniswapExchange(token); + exchange.setup(token); + getExchange[token] = address(exchange); + getToken[address(exchange)] = token; + uint256 tokenId = tokenCount + 1; + tokenCount = tokenId; + getTokenWithId[tokenId] = token; + emit NewExchange(token, address(exchange)); + return address(exchange); + } +} diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol new file mode 100644 index 000000000..0a71ade4c --- /dev/null +++ b/contracts/interfaces/IERC20.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.5.11; + +/** + * @title ERC20 interface + * @dev see https://eips.ethereum.org/EIPS/eip-20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + function totalSupply() external view returns (uint256); + function balanceOf(address who) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/contracts/interfaces/IUniswapExchange.sol b/contracts/interfaces/IUniswapExchange.sol new file mode 100644 index 000000000..ed2bc589e --- /dev/null +++ b/contracts/interfaces/IUniswapExchange.sol @@ -0,0 +1,254 @@ +pragma solidity ^0.5.0; + +interface IUniswapExchange { + event TokenPurchase(address indexed buyer, uint256 indexed eth_sold, uint256 indexed tokens_bought); + event EthPurchase(address indexed buyer, uint256 indexed tokens_sold, uint256 indexed eth_bought); + event AddLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount); + event RemoveLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount); + + /** + * @notice Convert ETH to Tokens. + * @dev User specifies exact input (msg.value). + * @dev User cannot specify minimum output or deadline. + */ + function () external payable; + + /** + * @dev Pricing function for converting between ETH && Tokens. + * @param inputAmount Amount of ETH or Tokens being sold. + * @param inputReserve Amount of ETH or Tokens (input type) in exchange reserves. + * @param outputReserve Amount of ETH or Tokens (output type) in exchange reserves. + * @return Amount of ETH or Tokens bought. + */ + function getInputPrice(uint256 inputAmount, uint256 inputReserve, uint256 outputReserve) external view returns (uint256); + + /** + * @dev Pricing function for converting between ETH && Tokens. + * @param outputAmount Amount of ETH or Tokens being bought. + * @param inputReserve Amount of ETH or Tokens (input type) in exchange reserves. + * @param outputReserve Amount of ETH or Tokens (output type) in exchange reserves. + * @return Amount of ETH or Tokens sold. + */ + function getOutputPrice(uint256 outputAmount, uint256 inputReserve, uint256 outputReserve) external view returns (uint256); + + + /** + * @notice Convert ETH to Tokens. + * @dev User specifies exact input (msg.value) && minimum output. + * @param minTokens Minimum Tokens bought. + * @param deadline Time after which this transaction can no longer be executed. + * @return Amount of Tokens bought. + */ + function ethToTokenSwapInput(uint256 minTokens, uint256 deadline) external payable returns (uint256); + + /** + * @notice Convert ETH to Tokens && transfers Tokens to recipient. + * @dev User specifies exact input (msg.value) && minimum output + * @param minTokens Minimum Tokens bought. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output Tokens. + * @return Amount of Tokens bought. + */ + function ethToTokenTransferInput(uint256 minTokens, uint256 deadline, address recipient) external payable returns(uint256); + + + /** + * @notice Convert ETH to Tokens. + * @dev User specifies maximum input (msg.value) && exact output. + * @param tokensBought Amount of tokens bought. + * @param deadline Time after which this transaction can no longer be executed. + * @return Amount of ETH sold. + */ + function ethToTokenSwapOutput(uint256 tokensBought, uint256 deadline) external payable returns(uint256); + + /** + * @notice Convert ETH to Tokens && transfers Tokens to recipient. + * @dev User specifies maximum input (msg.value) && exact output. + * @param tokensBought Amount of tokens bought. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output Tokens. + * @return Amount of ETH sold. + */ + function ethToTokenTransferOutput(uint256 tokensBought, uint256 deadline, address recipient) external payable returns (uint256); + + /** + * @notice Convert Tokens to ETH. + * @dev User specifies exact input && minimum output. + * @param tokensSold Amount of Tokens sold. + * @param minEth Minimum ETH purchased. + * @param deadline Time after which this transaction can no longer be executed. + * @return Amount of ETH bought. + */ + function tokenToEthSwapInput(uint256 tokensSold, uint256 minEth, uint256 deadline) external returns (uint256); + + /** + * @notice Convert Tokens to ETH && transfers ETH to recipient. + * @dev User specifies exact input && minimum output. + * @param tokensSold Amount of Tokens sold. + * @param minEth Minimum ETH purchased. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output ETH. + * @return Amount of ETH bought. + */ + function tokenToEthTransferInput(uint256 tokensSold, uint256 minEth, uint256 deadline, address recipient) external returns (uint256); + + /** + * @notice Convert Tokens to ETH. + * @dev User specifies maximum input && exact output. + * @param ethBought Amount of ETH purchased. + * @param maxTokens Maximum Tokens sold. + * @param deadline Time after which this transaction can no longer be executed. + * @return Amount of Tokens sold. + */ + function tokenToEthSwapOutput(uint256 ethBought, uint256 maxTokens, uint256 deadline) external returns (uint256); + + /** + * @notice Convert Tokens to ETH && transfers ETH to recipient. + * @dev User specifies maximum input && exact output. + * @param ethBought Amount of ETH purchased. + * @param maxTokens Maximum Tokens sold. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output ETH. + * @return Amount of Tokens sold. + */ + function tokenToEthTransferOutput(uint256 ethBought, uint256 maxTokens, uint256 deadline, address recipient) external returns (uint256); + + /** + * @notice Convert Tokens (token) to Tokens (tokenAddr). + * @dev User specifies exact input && minimum output. + * @param tokensSold Amount of Tokens sold. + * @param minTokensBought Minimum Tokens (tokenAddr) purchased. + * @param minEthBought Minimum ETH purchased as intermediary. + * @param deadline Time after which this transaction can no longer be executed. + * @param tokenAddr The address of the token being purchased. + * @return Amount of Tokens (tokenAddr) bought. + */ + function tokenToTokenSwapInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256 minEthBought, + uint256 deadline, + address tokenAddr) + external returns (uint256); + + /** + * @notice Convert Tokens (token) to Tokens (tokenAddr) && transfers + * Tokens (tokenAddr) to recipient. + * @dev User specifies exact input && minimum output. + * @param tokensSold Amount of Tokens sold. + * @param minTokensBought Minimum Tokens (tokenAddr) purchased. + * @param minEthBought Minimum ETH purchased as intermediary. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output ETH. + * @param tokenAddr The address of the token being purchased. + * @return Amount of Tokens (tokenAddr) bought. + */ + function tokenToTokenTransferInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256 minEthBought, + uint256 deadline, + address recipient, + address tokenAddr) + external returns (uint256); + + + /** + * @notice Convert Tokens (token) to Tokens (tokenAddr). + * @dev User specifies maximum input && exact output. + * @param tokensBought Amount of Tokens (tokenAddr) bought. + * @param maxTokensSold Maximum Tokens (token) sold. + * @param maxEthSold Maximum ETH purchased as intermediary. + * @param deadline Time after which this transaction can no longer be executed. + * @param tokenAddr The address of the token being purchased. + * @return Amount of Tokens (token) sold. + */ + function tokenToTokenSwapOutput( + uint256 tokensBought, + uint256 maxTokensSold, + uint256 maxEthSold, + uint256 deadline, + address tokenAddr) + external returns (uint256); + + /** + * @notice Convert Tokens (token) to Tokens (tokenAddr) && transfers + * Tokens (tokenAddr) to recipient. + * @dev User specifies maximum input && exact output. + * @param tokensBought Amount of Tokens (tokenAddr) bought. + * @param maxTokensSold Maximum Tokens (token) sold. + * @param maxEthSold Maximum ETH purchased as intermediary. + * @param deadline Time after which this transaction can no longer be executed. + * @param recipient The address that receives output ETH. + * @param tokenAddr The address of the token being purchased. + * @return Amount of Tokens (token) sold. + */ + function tokenToTokenTransferOutput( + uint256 tokensBought, + uint256 maxTokensSold, + uint256 maxEthSold, + uint256 deadline, + address recipient, + address tokenAddr) + external returns (uint256); + + + /** + * @notice Public price function for ETH to Token trades with an exact input. + * @param ethSold Amount of ETH sold. + * @return Amount of Tokens that can be bought with input ETH. + */ + function getEthToTokenInputPrice(uint256 ethSold) external view returns (uint256); + + /** + * @notice Public price function for ETH to Token trades with an exact output. + * @param tokensBought Amount of Tokens bought. + * @return Amount of ETH needed to buy output Tokens. + */ + function getEthToTokenOutputPrice(uint256 tokensBought) external view returns (uint256); + + /** + * @notice Public price function for Token to ETH trades with an exact input. + * @param tokensSold Amount of Tokens sold. + * @return Amount of ETH that can be bought with input Tokens. + */ + function getTokenToEthInputPrice(uint256 tokensSold) external view returns (uint256); + + /** + * @notice Public price function for Token to ETH trades with an exact output. + * @param ethBought Amount of output ETH. + * @return Amount of Tokens needed to buy output ETH. + */ + function getTokenToEthOutputPrice(uint256 ethBought) external view returns (uint256); + + /** + * @return Address of Token that is sold on this exchange. + */ + function tokenAddress() external view returns (address); + + /** + * @return Address of factory that created this exchange. + */ + function factoryAddress() external view returns (address); + + + /** + * @notice Deposit ETH && Tokens (token) at current ratio to mint UNI tokens. + * @dev minLiquidity does nothing when total UNI supply is 0. + * @param minLiquidity Minimum number of UNI sender will mint if total UNI supply is greater than 0. + * @param maxTokens Maximum number of tokens deposited. Deposits max amount if total UNI supply is 0. + * @param deadline Time after which this transaction can no longer be executed. + * @return The amount of UNI minted. + */ + function addLiquidity(uint256 minLiquidity, uint256 maxTokens, uint256 deadline) external payable returns (uint256); + + /** + * @dev Burn UNI tokens to withdraw ETH && Tokens at current ratio. + * @param amount Amount of UNI burned. + * @param minEth Minimum ETH withdrawn. + * @param minTokens Minimum Tokens withdrawn. + * @param deadline Time after which this transaction can no longer be executed. + * @return The amount of ETH && Tokens withdrawn. + */ + function removeLiquidity(uint256 amount, uint256 minEth, uint256 minTokens, uint256 deadline) external returns (uint256, uint256); +} diff --git a/contracts/interfaces/IUniswapFactory.sol b/contracts/interfaces/IUniswapFactory.sol new file mode 100644 index 000000000..4ca60708d --- /dev/null +++ b/contracts/interfaces/IUniswapFactory.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.5.11; + +interface IUniswapFactory { + event NewExchange(address indexed token, address indexed exchange); + + function initializeFactory(address template) external; + function createExchange(address token) external returns (address payable); + function getExchange(address token) external view returns (address payable); + function getToken(address token) external view returns (address); + function getTokenWihId(uint256 token_id) external view returns (address); +} diff --git a/contracts/test_contracts/TestToken.sol b/contracts/test_contracts/TestToken.sol new file mode 100644 index 000000000..7e452647f --- /dev/null +++ b/contracts/test_contracts/TestToken.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.5.11; +import "../SafeMath.sol"; + + +contract TestERC20 { + using SafeMath for uint256; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + string public name; + string public symbol; + uint256 public decimals; + uint256 public totalSupply; + uint256 internal constant MAX_UINT256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + + constructor(string memory _name, string memory _symbol, uint256 _decimals, uint256 supply) public { + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = supply; + balanceOf[msg.sender] = supply; + } + + function transfer(address to, uint256 value) public returns (bool) { + balanceOf[msg.sender] = balanceOf[msg.sender].sub(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(msg.sender, to, value); + return true; + } + + function transferFrom(address from, address to, uint256 value) public returns (bool) { + if (allowance[from][msg.sender] < MAX_UINT256) { + allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); + } + balanceOf[from] = balanceOf[from].sub(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(from, to, value); + return true; + } + + function approve(address spender, uint256 value) public returns (bool) { + allowance[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + function burn(uint256 value) public { + totalSupply = totalSupply.sub(value); + balanceOf[msg.sender] = balanceOf[msg.sender].sub(value); + emit Transfer(msg.sender, address(0), value); + } +} diff --git a/contracts/test_contracts/Token.sol b/contracts/test_contracts/Token.sol deleted file mode 100644 index 250de65b4..000000000 --- a/contracts/test_contracts/Token.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.5.11; - -import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; - - -// Example class - a mock class using delivering from ERC20 -contract Token is ERC20, ERC20Detailed { - constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialBalance) - public - ERC20Detailed(_name, _symbol, _decimals) - { - super._mint(msg.sender, initialBalance); - } -} diff --git a/test/testExchange.js b/test/testExchange.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/testFactory.js b/test/testFactory.js new file mode 100644 index 000000000..e69de29bb