diff --git a/contracts/UniswapV2.sol b/contracts/UniswapV2.sol index 82f0419..e38458b 100644 --- a/contracts/UniswapV2.sol +++ b/contracts/UniswapV2.sol @@ -42,11 +42,10 @@ contract UniswapV2 is ERC20("Uniswap V2", "UNI-V2", 18, 0) { reentrancyLock = false; } - constructor(address _tokenA, address _tokenB) public { - require(address(_tokenA) != address(0) && _tokenB != address(0), "Uniswap: INVALID_ADDRESS"); + constructor() public { factory = msg.sender; - tokenA = _tokenA; - tokenB = _tokenB; + // tokenA = _tokenA; + // tokenB = _tokenB; } function () external {} diff --git a/contracts/UniswapV2Factory.sol b/contracts/UniswapV2Factory.sol index 62229c2..33f67bc 100644 --- a/contracts/UniswapV2Factory.sol +++ b/contracts/UniswapV2Factory.sol @@ -1,45 +1,62 @@ -// TODO review, create2 +// TODO create2 exchanges pragma solidity 0.5.12; -import "./UniswapV2.sol"; +import "./interfaces/IUniswapV2Factory.sol"; -contract UniswapV2Factory { - event ExchangeCreated(address indexed token0, address indexed token1, address exchange); +contract UniswapV2Factory is IUniswapV2Factory { + event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint256 exchangeCount); - mapping (address => address) internal exchangeToToken0; - mapping (address => address) internal exchangeToToken1; - mapping (address => mapping(address => address)) internal token0ToToken1ToExchange; - - function orderTokens(address tokenA, address tokenB) internal pure returns (address, address) { - address token0 = tokenA < tokenB ? tokenA : tokenB; - address token1 = tokenA < tokenB ? tokenB : tokenA; - return (token0, token1); + struct Pair { + address token0; + address token1; } - function createExchange(address tokenA, address tokenB) public returns (address) { - require(tokenA != tokenB, "UniswapV2Factory: INVALID_PAIR"); - require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: NO_ZERO_ADDRESS_TOKENS"); + bytes private exchangeBytecode; + uint256 public exchangeCount; + mapping (address => Pair) private exchangeToPair; + mapping (address => mapping(address => address)) private token0ToToken1ToExchange; - (address token0, address token1) = orderTokens(tokenA, tokenB); - - require(token0ToToken1ToExchange[token0][token1] == address(0), "UniswapV2Factory: EXCHANGE_EXISTS"); - - UniswapV2 exchange = new UniswapV2(token0, token1); - exchangeToToken0[address(exchange)] = token0; - exchangeToToken1[address(exchange)] = token1; - token0ToToken1ToExchange[token0][token1] = address(exchange); - - emit ExchangeCreated(token0, token1, address(exchange)); - - return address(exchange); + constructor(bytes memory _exchangeBytecode) public { + require(_exchangeBytecode.length >= 0x20, "UniswapV2Factory: SHORT_BYTECODE"); + exchangeBytecode = _exchangeBytecode; } - function getExchange(address tokenA, address tokenB) public view returns (address) { - (address token0, address token1) = orderTokens(tokenA, tokenB); - return token0ToToken1ToExchange[token0][token1]; + function orderTokens(address tokenA, address tokenB) private pure returns (Pair memory pair) { + pair = tokenA < tokenB ? Pair(tokenA, tokenB) : Pair(tokenB, tokenA); } - function getTokens(address exchange) public view returns (address, address) { - return (exchangeToToken0[exchange], exchangeToToken1[exchange]); + function getTokens(address exchange) public view returns (address token0, address token1) { + Pair storage pair = exchangeToPair[exchange]; + (token0, token1) = (pair.token0, pair.token1); + } + + function getExchange(address tokenA, address tokenB) public view returns (address exchange) { + Pair memory pair = orderTokens(tokenA, tokenB); + exchange = token0ToToken1ToExchange[pair.token0][pair.token1]; + } + + function createExchange(address tokenA, address tokenB) public returns (address exchange) { + require(tokenA != tokenB, "UniswapV2Factory: SAME_TOKEN"); + require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: ZERO_ADDRESS_TOKEN"); + + Pair memory pair = orderTokens(tokenA, tokenB); + + require(token0ToToken1ToExchange[pair.token0][pair.token1] == address(0), "UniswapV2Factory: EXCHANGE_EXISTS"); + + bytes memory exchangeBytecodeMemory = exchangeBytecode; + uint256 exchangeBytecodeLength = exchangeBytecode.length; + bytes32 salt = keccak256(abi.encodePacked(pair.token0, pair.token1)); + assembly { + exchange := create2( + 0, + add(exchangeBytecodeMemory, 0x20), + exchangeBytecodeLength, + salt + ) + } + exchangeToPair[exchange] = pair; + token0ToToken1ToExchange[pair.token0][pair.token1] = exchange; + + emit ExchangeCreated(pair.token0, pair.token1, exchange, exchangeCount++); } } diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 346e243..95d76af 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -1,6 +1,9 @@ pragma solidity 0.5.12; interface IERC20 { + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); @@ -8,9 +11,6 @@ interface IERC20 { function balanceOf(address owner) 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); - function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function burn(uint256 value) external; diff --git a/contracts/interfaces/IUniswapV2Factory.sol b/contracts/interfaces/IUniswapV2Factory.sol new file mode 100644 index 0000000..07d9f1d --- /dev/null +++ b/contracts/interfaces/IUniswapV2Factory.sol @@ -0,0 +1,11 @@ +pragma solidity 0.5.12; + +interface IUniswapV2Factory { + event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint256 exchangeNumber); + + function exchangeCount() external view returns (uint256); + function getTokens(address exchange) external view returns (address token0, address token1); + function getExchange(address tokenA, address tokenB) external view returns (address exchange); + + function createExchange(address tokenA, address tokenB) external returns (address exchange); +} diff --git a/test/ERC20.ts b/test/ERC20.ts index c962d62..f175fc8 100644 --- a/test/ERC20.ts +++ b/test/ERC20.ts @@ -4,7 +4,6 @@ import { Contract } from 'ethers' import { BigNumber, bigNumberify } from 'ethers/utils' import ERC20 from '../build/ERC20.json' -import {} from 'ethers/utils' chai.use(solidity) const { expect } = chai @@ -13,6 +12,7 @@ const decimalize = (n: number): BigNumber => bigNumberify(n).mul(bigNumberify(10 const name = 'Mock ERC20' const symbol = 'MOCK' +const decimals = 18 const totalSupply = decimalize(1000) describe('ERC20', () => { @@ -21,7 +21,7 @@ describe('ERC20', () => { let token: Contract beforeEach(async () => { - token = await deployContract(wallet, ERC20, [name, symbol, totalSupply]) + token = await deployContract(wallet, ERC20, [name, symbol, decimals, totalSupply]) }) it('name, symbol, decimals, totalSupply, balanceOf', async () => { diff --git a/test/UniswapV2Factory.ts b/test/UniswapV2Factory.ts new file mode 100644 index 0000000..aeaef21 --- /dev/null +++ b/test/UniswapV2Factory.ts @@ -0,0 +1,54 @@ +import path from 'path' +import chai from 'chai' +import { solidity, createMockProvider, getWallets, deployContract } from 'ethereum-waffle' +import { Contract } from 'ethers' +import { keccak256, solidityPack, getAddress } from 'ethers/utils' + +import UniswapV2 from '../build/UniswapV2.json' +import UniswapV2Factory from '../build/UniswapV2Factory.json' + +chai.use(solidity) +const { expect } = chai + +const dummyTokens = ['0x1000000000000000000000000000000000000000', '0x2000000000000000000000000000000000000000'] + +describe('UniswapV2Factory', () => { + const provider = createMockProvider(path.join(__dirname, '..', 'waffle.json')) + const [wallet] = getWallets(provider) + let bytecode: string + let factory: Contract + + it('can deploy factory', async () => { + bytecode = `0x${UniswapV2.evm.bytecode.object}` + + factory = await deployContract(wallet, UniswapV2Factory, [bytecode], { + gasLimit: (provider._web3Provider as any).options.gasLimit + }) + + expect(await factory.exchangeCount()).to.eq(0) + }) + + it('can create exchange', async () => { + const expectedAddress = getAddress( + `0x${keccak256( + [ + '0xff', + factory.address.slice(2), + keccak256(solidityPack(['address', 'address'], dummyTokens)).slice(2), + keccak256(bytecode).slice(2) + ].join('') + ).slice(-40)}` + ) + + await expect(factory.createExchange(...dummyTokens)) + .to.emit(factory, 'ExchangeCreated') + .withArgs(...[...dummyTokens, expectedAddress, 0]) + + expect(await factory.exchangeCount()).to.eq(1) + expect(await factory.getTokens(expectedAddress)).to.deep.eq(dummyTokens) + expect(await factory.getExchange(...dummyTokens)).to.eq(expectedAddress) + + const exchange = new Contract(expectedAddress, UniswapV2.abi, provider) + expect(await exchange.factory()).to.eq(factory.address) + }) +}) diff --git a/waffle.json b/waffle.json index 5d8e21f..25cc455 100644 --- a/waffle.json +++ b/waffle.json @@ -1,3 +1,15 @@ { - "solcVersion": "./node_modules/solc" + "solcVersion": "./node_modules/solc", + "compilerOptions": { + "evmVersion": "constantinople", + "optimizer": { + "enabled": true, + "runs": 1000 + } + }, + "ganacheOptions": { + "hardfork": "constantinople", + "mnemonic": "horn horn horn horn horn horn horn horn horn horn horn horn", + "gasLimit": "0x7A1200" + } }