run everything on constantinople
turn on compiler optimization don't hardcode 18 decimals add create2 name return values in factory
This commit is contained in:
@ -42,11 +42,10 @@ contract UniswapV2 is ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
|||||||
reentrancyLock = false;
|
reentrancyLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(address _tokenA, address _tokenB) public {
|
constructor() public {
|
||||||
require(address(_tokenA) != address(0) && _tokenB != address(0), "Uniswap: INVALID_ADDRESS");
|
|
||||||
factory = msg.sender;
|
factory = msg.sender;
|
||||||
tokenA = _tokenA;
|
// tokenA = _tokenA;
|
||||||
tokenB = _tokenB;
|
// tokenB = _tokenB;
|
||||||
}
|
}
|
||||||
|
|
||||||
function () external {}
|
function () external {}
|
||||||
|
|||||||
@ -1,45 +1,62 @@
|
|||||||
// TODO review, create2
|
// TODO create2 exchanges
|
||||||
pragma solidity 0.5.12;
|
pragma solidity 0.5.12;
|
||||||
|
|
||||||
import "./UniswapV2.sol";
|
import "./interfaces/IUniswapV2Factory.sol";
|
||||||
|
|
||||||
contract UniswapV2Factory {
|
contract UniswapV2Factory is IUniswapV2Factory {
|
||||||
event ExchangeCreated(address indexed token0, address indexed token1, address exchange);
|
event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint256 exchangeCount);
|
||||||
|
|
||||||
mapping (address => address) internal exchangeToToken0;
|
struct Pair {
|
||||||
mapping (address => address) internal exchangeToToken1;
|
address token0;
|
||||||
mapping (address => mapping(address => address)) internal token0ToToken1ToExchange;
|
address token1;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExchange(address tokenA, address tokenB) public returns (address) {
|
bytes private exchangeBytecode;
|
||||||
require(tokenA != tokenB, "UniswapV2Factory: INVALID_PAIR");
|
uint256 public exchangeCount;
|
||||||
require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: NO_ZERO_ADDRESS_TOKENS");
|
mapping (address => Pair) private exchangeToPair;
|
||||||
|
mapping (address => mapping(address => address)) private token0ToToken1ToExchange;
|
||||||
|
|
||||||
(address token0, address token1) = orderTokens(tokenA, tokenB);
|
constructor(bytes memory _exchangeBytecode) public {
|
||||||
|
require(_exchangeBytecode.length >= 0x20, "UniswapV2Factory: SHORT_BYTECODE");
|
||||||
require(token0ToToken1ToExchange[token0][token1] == address(0), "UniswapV2Factory: EXCHANGE_EXISTS");
|
exchangeBytecode = _exchangeBytecode;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExchange(address tokenA, address tokenB) public view returns (address) {
|
function orderTokens(address tokenA, address tokenB) private pure returns (Pair memory pair) {
|
||||||
(address token0, address token1) = orderTokens(tokenA, tokenB);
|
pair = tokenA < tokenB ? Pair(tokenA, tokenB) : Pair(tokenB, tokenA);
|
||||||
return token0ToToken1ToExchange[token0][token1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTokens(address exchange) public view returns (address, address) {
|
function getTokens(address exchange) public view returns (address token0, address token1) {
|
||||||
return (exchangeToToken0[exchange], exchangeToToken1[exchange]);
|
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++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
pragma solidity 0.5.12;
|
pragma solidity 0.5.12;
|
||||||
|
|
||||||
interface IERC20 {
|
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 name() external view returns (string memory);
|
||||||
function symbol() external view returns (string memory);
|
function symbol() external view returns (string memory);
|
||||||
function decimals() external view returns (uint8);
|
function decimals() external view returns (uint8);
|
||||||
@ -8,9 +11,6 @@ interface IERC20 {
|
|||||||
function balanceOf(address owner) external view returns (uint256);
|
function balanceOf(address owner) external view returns (uint256);
|
||||||
function allowance(address owner, address spender) 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 transfer(address to, uint256 value) external returns (bool);
|
||||||
function transferFrom(address from, address to, uint256 value) external returns (bool);
|
function transferFrom(address from, address to, uint256 value) external returns (bool);
|
||||||
function burn(uint256 value) external;
|
function burn(uint256 value) external;
|
||||||
|
|||||||
11
contracts/interfaces/IUniswapV2Factory.sol
Normal file
11
contracts/interfaces/IUniswapV2Factory.sol
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import { Contract } from 'ethers'
|
|||||||
import { BigNumber, bigNumberify } from 'ethers/utils'
|
import { BigNumber, bigNumberify } from 'ethers/utils'
|
||||||
|
|
||||||
import ERC20 from '../build/ERC20.json'
|
import ERC20 from '../build/ERC20.json'
|
||||||
import {} from 'ethers/utils'
|
|
||||||
|
|
||||||
chai.use(solidity)
|
chai.use(solidity)
|
||||||
const { expect } = chai
|
const { expect } = chai
|
||||||
@ -13,6 +12,7 @@ const decimalize = (n: number): BigNumber => bigNumberify(n).mul(bigNumberify(10
|
|||||||
|
|
||||||
const name = 'Mock ERC20'
|
const name = 'Mock ERC20'
|
||||||
const symbol = 'MOCK'
|
const symbol = 'MOCK'
|
||||||
|
const decimals = 18
|
||||||
const totalSupply = decimalize(1000)
|
const totalSupply = decimalize(1000)
|
||||||
|
|
||||||
describe('ERC20', () => {
|
describe('ERC20', () => {
|
||||||
@ -21,7 +21,7 @@ describe('ERC20', () => {
|
|||||||
let token: Contract
|
let token: Contract
|
||||||
|
|
||||||
beforeEach(async () => {
|
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 () => {
|
it('name, symbol, decimals, totalSupply, balanceOf', async () => {
|
||||||
|
|||||||
54
test/UniswapV2Factory.ts
Normal file
54
test/UniswapV2Factory.ts
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
14
waffle.json
14
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user