diff --git a/contracts/UniswapV2ERC20.sol b/contracts/UniswapV2ERC20.sol index 65b0f46..404a8be 100644 --- a/contracts/UniswapV2ERC20.sol +++ b/contracts/UniswapV2ERC20.sol @@ -13,7 +13,6 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { mapping(address => uint) public balanceOf; mapping(address => mapping(address => uint)) public allowance; - uint public constant MINIMUM_TOTAL_SUPPLY = 10**4; bytes32 public DOMAIN_SEPARATOR; // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; @@ -39,18 +38,14 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { } function _mint(address to, uint value) internal { - uint _totalSupply = totalSupply.add(value); - require(_totalSupply >= MINIMUM_TOTAL_SUPPLY, 'UniswapV2: MINIMUM_TOTAL_SUPPLY'); - totalSupply = _totalSupply; + totalSupply = totalSupply.add(value); balanceOf[to] = balanceOf[to].add(value); emit Transfer(address(0), to, value); } function _burn(address from, uint value) internal { - uint _totalSupply = totalSupply.sub(value); - require(_totalSupply == 0 || _totalSupply >= MINIMUM_TOTAL_SUPPLY, 'UniswapV2: MINIMUM_TOTAL_SUPPLY'); balanceOf[from] = balanceOf[from].sub(value); - totalSupply = _totalSupply; + totalSupply = totalSupply.sub(value); emit Transfer(from, address(0), value); } diff --git a/contracts/UniswapV2Exchange.sol b/contracts/UniswapV2Exchange.sol index 3efa62c..9727cf7 100644 --- a/contracts/UniswapV2Exchange.sol +++ b/contracts/UniswapV2Exchange.sol @@ -11,7 +11,8 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 { using SafeMath for uint; using UQ112x112 for uint224; - bytes4 public constant selector = bytes4(keccak256(bytes('transfer(address,uint256)'))); + uint public constant MINIMUM_LIQUIDITY = 10**3; + bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); address public factory; address public token0; @@ -42,7 +43,7 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 { } function _safeTransfer(address token, address to, uint value) private { - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(selector, to, value)); + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); } @@ -107,9 +108,12 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 { bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee - liquidity = _totalSupply == 0 - ? Math.sqrt(amount0.mul(amount1)) - : Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); + if (_totalSupply == 0) { + liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); + _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens + } else { + liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); + } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); diff --git a/contracts/interfaces/IUniswapV2ERC20.sol b/contracts/interfaces/IUniswapV2ERC20.sol index 1b4e2f5..230eb4a 100644 --- a/contracts/interfaces/IUniswapV2ERC20.sol +++ b/contracts/interfaces/IUniswapV2ERC20.sol @@ -15,7 +15,6 @@ interface IUniswapV2ERC20 { function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); - function MINIMUM_TOTAL_SUPPLY() external pure returns (uint); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); diff --git a/contracts/interfaces/IUniswapV2Exchange.sol b/contracts/interfaces/IUniswapV2Exchange.sol index 95f6efe..2f37c9a 100644 --- a/contracts/interfaces/IUniswapV2Exchange.sol +++ b/contracts/interfaces/IUniswapV2Exchange.sol @@ -15,7 +15,6 @@ interface IUniswapV2Exchange { function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); - function MINIMUM_TOTAL_SUPPLY() external pure returns (uint); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); @@ -27,7 +26,7 @@ interface IUniswapV2Exchange { event Swap(address indexed sender, address indexed tokenIn, uint amountIn, uint amountOut, address indexed to); event Sync(uint112 reserve0, uint112 reserve1); - function selector() external pure returns (bytes4); + function MINIMUM_LIQUIDITY() external pure returns (uint); function factory() external view returns (address); function token0() external view returns (address); function token1() external view returns (address); diff --git a/test/UniswapV2Exchange.spec.ts b/test/UniswapV2Exchange.spec.ts index e8f7c69..b6f3ccb 100644 --- a/test/UniswapV2Exchange.spec.ts +++ b/test/UniswapV2Exchange.spec.ts @@ -7,6 +7,8 @@ import { expandTo18Decimals, mineBlock, encodePrice } from './shared/utilities' import { exchangeFixture } from './shared/fixtures' import { AddressZero } from 'ethers/constants' +const MINIMUM_LIQUIDITY = bigNumberify(10).pow(3) + chai.use(solidity) const overrides = { @@ -43,14 +45,17 @@ describe('UniswapV2Exchange', () => { const expectedLiquidity = expandTo18Decimals(2) await expect(exchange.mint(wallet.address, overrides)) .to.emit(exchange, 'Transfer') - .withArgs(AddressZero, wallet.address, expectedLiquidity) + .withArgs(AddressZero, AddressZero, MINIMUM_LIQUIDITY) + // commented out because of this bug: https://github.com/EthWorks/Waffle/issues/100 + // .to.emit(exchange, 'Transfer') + // .withArgs(AddressZero, wallet.address, expectedLiquidity) .to.emit(exchange, 'Sync') .withArgs(token0Amount, token1Amount) .to.emit(exchange, 'Mint') .withArgs(wallet.address, token0Amount, token1Amount) expect(await exchange.totalSupply()).to.eq(expectedLiquidity) - expect(await exchange.balanceOf(wallet.address)).to.eq(expectedLiquidity) + expect(await exchange.balanceOf(wallet.address)).to.eq(expectedLiquidity.sub(MINIMUM_LIQUIDITY)) expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount) expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount) const reserves = await exchange.getReserves() @@ -63,31 +68,26 @@ describe('UniswapV2Exchange', () => { await token1.transfer(exchange.address, token1Amount) await exchange.mint(wallet.address, overrides) } + const testCases: BigNumber[][] = [ + [1, 5, 10, '1662497915624478906'], + [1, 10, 5, '453305446940074565'], - it('getInputPrice', async () => { - const testCases: BigNumber[][] = [ - [1, 5, 10, '1662497915624478906'], - [1, 10, 5, '453305446940074565'], + [2, 5, 10, '2851015155847869602'], + [2, 10, 5, '831248957812239453'], - [2, 5, 10, '2851015155847869602'], - [2, 10, 5, '831248957812239453'], - - [1, 10, 10, '906610893880149131'], - [1, 100, 100, '987158034397061298'], - [1, 1000, 1000, '996006981039903216'] - ].map(a => a.map((n, i) => (i === 3 ? bigNumberify(n) : expandTo18Decimals(n as number)))) - - for (let testCase of testCases) { + [1, 10, 10, '906610893880149131'], + [1, 100, 100, '987158034397061298'], + [1, 1000, 1000, '996006981039903216'] + ].map(a => a.map((n, i) => (i === 3 ? bigNumberify(n) : expandTo18Decimals(n as number)))) + testCases.forEach((testCase, i) => { + it(`getInputPrice:${i}`, async () => { await addLiquidity(testCase[1], testCase[2]) await token0.transfer(exchange.address, testCase[0]) await expect(exchange.swap(token0.address, testCase[3].add(1), wallet.address, overrides)).to.be.revertedWith( 'UniswapV2: K' ) await exchange.swap(token0.address, testCase[3], wallet.address, overrides) - const totalSupply = await exchange.totalSupply() - await exchange.transfer(exchange.address, totalSupply) - await exchange.burn(wallet.address, overrides) - } + }) }) it('swap:token0', async () => { @@ -167,28 +167,28 @@ describe('UniswapV2Exchange', () => { await addLiquidity(token0Amount, token1Amount) const expectedLiquidity = expandTo18Decimals(3) - await exchange.transfer(exchange.address, expectedLiquidity) + await exchange.transfer(exchange.address, expectedLiquidity.sub(MINIMUM_LIQUIDITY)) await expect(exchange.burn(wallet.address, overrides)) .to.emit(exchange, 'Transfer') - .withArgs(exchange.address, AddressZero, expectedLiquidity) + .withArgs(exchange.address, AddressZero, expectedLiquidity.sub(MINIMUM_LIQUIDITY)) // commented out because of this bug: https://github.com/EthWorks/Waffle/issues/100 // .to.emit(token0, 'Transfer') // .withArgs(exchange.address, wallet.address, token0Amount) // .to.emit(token1, 'Transfer') // .withArgs(exchange.address, wallet.address, token1Amount) .to.emit(exchange, 'Sync') - .withArgs(0, 0) + .withArgs(1000, 1000) .to.emit(exchange, 'Burn') - .withArgs(wallet.address, token0Amount, token1Amount, wallet.address) + .withArgs(wallet.address, token0Amount.sub(1000), token1Amount.sub(1000), wallet.address) expect(await exchange.balanceOf(wallet.address)).to.eq(0) - expect(await exchange.totalSupply()).to.eq(0) - expect(await token0.balanceOf(exchange.address)).to.eq(0) - expect(await token1.balanceOf(exchange.address)).to.eq(0) + expect(await exchange.totalSupply()).to.eq(MINIMUM_LIQUIDITY) + expect(await token0.balanceOf(exchange.address)).to.eq(1000) + expect(await token1.balanceOf(exchange.address)).to.eq(1000) const totalSupplyToken0 = await token0.totalSupply() const totalSupplyToken1 = await token1.totalSupply() - expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0) - expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1) + expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0.sub(1000)) + expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1.sub(1000)) }) it('price{0,1}CumulativeLast', async () => { @@ -235,9 +235,9 @@ describe('UniswapV2Exchange', () => { await exchange.swap(token1.address, expectedOutputAmount, wallet.address, overrides) const expectedLiquidity = expandTo18Decimals(1000) - await exchange.transfer(exchange.address, expectedLiquidity) + await exchange.transfer(exchange.address, expectedLiquidity.sub(MINIMUM_LIQUIDITY)) await exchange.burn(wallet.address, overrides) - expect(await exchange.totalSupply()).to.eq(0) + expect(await exchange.totalSupply()).to.eq(MINIMUM_LIQUIDITY) }) it('feeTo:on', async () => { @@ -253,12 +253,14 @@ describe('UniswapV2Exchange', () => { await exchange.swap(token1.address, expectedOutputAmount, wallet.address, overrides) const expectedLiquidity = expandTo18Decimals(1000) - await exchange.transfer(exchange.address, expectedLiquidity) + await exchange.transfer(exchange.address, expectedLiquidity.sub(MINIMUM_LIQUIDITY)) await exchange.burn(wallet.address, overrides) - expect(await exchange.totalSupply()).to.eq('249750499251388') + expect(await exchange.totalSupply()).to.eq(MINIMUM_LIQUIDITY.add('249750499251388')) expect(await exchange.balanceOf(other.address)).to.eq('249750499251388') - expect(await token0.balanceOf(exchange.address)).to.eq('249501683697446') - expect(await token1.balanceOf(exchange.address)).to.eq('250000187312968') + // using 1000 here instead of the symbolic MINIMUM_LIQUIDITY because the amounts only happen to be equal... + // ...because the initial liquidity amounts were equal + expect(await token0.balanceOf(exchange.address)).to.eq(bigNumberify(1000).add('249501683697445')) + expect(await token1.balanceOf(exchange.address)).to.eq(bigNumberify(1000).add('250000187312969')) }) })