diff --git a/contracts/UniswapV2.sol b/contracts/UniswapV2.sol index 9a8a794..c28a325 100644 --- a/contracts/UniswapV2.sol +++ b/contracts/UniswapV2.sol @@ -1,6 +1,7 @@ pragma solidity 0.5.14; import "./interfaces/IUniswapV2.sol"; +import "./interfaces/IUniswapV2Factory.sol"; import "./ERC20.sol"; import "./libraries/UQ112x112.sol"; import "./libraries/Math.sol"; @@ -12,6 +13,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { address public factory; address public token0; address public token1; + address public feeAddress; uint112 public reserve0; uint112 public reserve1; @@ -21,6 +23,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { uint private invariantLast; bool private notEntered = true; + bool private feeOn; event ReservesUpdated(uint112 reserve0, uint112 reserve1); event LiquidityMinted(address indexed sender, uint amount0, uint amount1); @@ -39,10 +42,11 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { blockNumberLast = uint32(block.number % 2**32); } - function initialize(address _token0, address _token1) external { + function initialize(address _token0, address _token1, address _feeAddress) external { require(msg.sender == factory && token0 == address(0) && token1 == address(0), "UniswapV2: FORBIDDEN"); token0 = _token0; token1 = _token1; + feeAddress = _feeAddress; } function safeTransfer(address token, address to, uint value) private { @@ -79,14 +83,17 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { // mint liquidity equivalent to 20% of accumulated fees function mintFeeLiquidity() private { - uint invariant = Math.sqrt(uint(reserve0).mul(reserve1)); - if (invariant > invariantLast) { - uint numerator = totalSupply.mul(invariant.sub(invariantLast)); - uint denominator = uint256(4).mul(invariant).add(invariantLast); - uint liquidity = numerator / denominator; - if (liquidity > 0) { - _mint(factory, liquidity); // factory is a placeholder + if (feeOn) { + uint invariant = Math.sqrt(uint(reserve0).mul(reserve1)); + if (invariant > invariantLast) { + uint numerator = totalSupply.mul(invariant.sub(invariantLast)); + uint denominator = uint256(4).mul(invariant).add(invariantLast); + uint liquidity = numerator / denominator; + if (liquidity > 0) _mint(feeAddress, liquidity); } + } else if (IUniswapV2Factory(factory).feeOn()) { + feeOn = true; + invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); } } @@ -105,7 +112,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { _mint(recipient, liquidity); update(balance0, balance1); - invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); + if (feeOn) invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); emit LiquidityMinted(msg.sender, amount0, amount1); } @@ -113,7 +120,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { uint liquidity = balanceOf[address(this)]; uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); - require(balance0 >= reserve0 && balance0 >= reserve1, "UniswapV2: INSUFFICIENT_BALANCES"); + require(balance0 >= reserve0 && balance1 >= reserve1, "UniswapV2: INSUFFICIENT_BALANCES"); mintFeeLiquidity(); amount0 = liquidity.mul(balance0) / totalSupply; // intentionally using balances not reserves @@ -124,7 +131,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { _burn(address(this), liquidity); update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); - invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); + if (feeOn) invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); emit LiquidityBurned(msg.sender, recipient, amount0, amount1); } @@ -161,6 +168,6 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { function sweep() external lock { mintFeeLiquidity(); - invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); + if (feeOn) invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); } } diff --git a/contracts/UniswapV2Factory.sol b/contracts/UniswapV2Factory.sol index e7219b8..13c0725 100644 --- a/contracts/UniswapV2Factory.sol +++ b/contracts/UniswapV2Factory.sol @@ -10,11 +10,15 @@ contract UniswapV2Factory is IUniswapV2Factory { mapping (address => address[2]) private _getTokens; address[] public exchanges; + address public feeAddress; + bool public feeOn; + event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint exchangeNumber); - constructor(bytes memory _exchangeBytecode) public { + constructor(bytes memory _exchangeBytecode, address _feeAddress) public { require(_exchangeBytecode.length >= 32, "UniswapV2Factory: SHORT_BYTECODE"); exchangeBytecode = _exchangeBytecode; + feeAddress = _feeAddress; } function sortTokens(address tokenA, address tokenB) public pure returns (address, address) { @@ -45,10 +49,20 @@ contract UniswapV2Factory is IUniswapV2Factory { assembly { // solium-disable-line security/no-inline-assembly exchange := create2(0, add(exchangeBytecodeMemory, 32), mload(exchangeBytecodeMemory), salt) } - UniswapV2(exchange).initialize(token0, token1); + UniswapV2(exchange).initialize(token0, token1, feeAddress); _getExchange[token0][token1] = exchange; _getTokens[exchange] = [token0, token1]; emit ExchangeCreated(token0, token1, exchange, exchanges.push(exchange)); } + + function setFeeAddress(address _feeAddress) public { + require(msg.sender == feeAddress, "UniswapV2Factory: FORBIDDEN"); + feeAddress = _feeAddress; + } + + function turnFeeOn() public { + require(msg.sender == feeAddress, "UniswapV2Factory: FORBIDDEN"); + feeOn = true; + } } diff --git a/contracts/interfaces/IUniswapV2Factory.sol b/contracts/interfaces/IUniswapV2Factory.sol index ce51cfa..f57df7f 100644 --- a/contracts/interfaces/IUniswapV2Factory.sol +++ b/contracts/interfaces/IUniswapV2Factory.sol @@ -5,6 +5,9 @@ interface IUniswapV2Factory { function exchangeBytecode() external view returns (bytes memory); + function feeAddress() external view returns (address); + function feeOn() external view returns (bool); + function sortTokens(address tokenA, address tokenB) external pure returns (address, address); function getExchange(address tokenA, address tokenB) external view returns (address); function getTokens(address exchange) external view returns (address, address); diff --git a/test/shared/fixtures.ts b/test/shared/fixtures.ts index 4f3d069..047905c 100644 --- a/test/shared/fixtures.ts +++ b/test/shared/fixtures.ts @@ -1,5 +1,6 @@ import { providers, Wallet, Contract } from 'ethers' import { deployContract } from 'ethereum-waffle' +import { AddressZero } from 'ethers/constants' import { expandTo18Decimals } from './utilities' @@ -14,7 +15,7 @@ export interface FactoryFixture { export async function factoryFixture(provider: providers.Web3Provider, [wallet]: Wallet[]): Promise { const bytecode = `0x${UniswapV2.evm.bytecode.object}` - const factory = await deployContract(wallet, UniswapV2Factory, [bytecode], { + const factory = await deployContract(wallet, UniswapV2Factory, [bytecode, AddressZero], { gasLimit: (provider._web3Provider as any).options.gasLimit })