diff --git a/README.md b/README.md index 922f2b7..b4baf5e 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,5 @@ yarn test - [OpenZeppelin ECDSA](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/81b1e4810761b088922dbd19a0642873ea581176/contracts/cryptography/ECDSA.sol) - [DAI token](https://github.com/makerdao/dss/blob/17be7db1c663d8069308c6b78fa5c5f9d71134a3/src/dai.sol) - [Coinmonks](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca) +- [Remco Bloemen](https://medium.com/wicketh/mathemagic-full-multiply-27650fec525d) +- [Remco Bloemen](https://medium.com/wicketh/mathemagic-512-bit-division-in-solidity-afa55870a65) diff --git a/contracts/UniswapV2.sol b/contracts/UniswapV2.sol index ff2c257..ff05162 100644 --- a/contracts/UniswapV2.sol +++ b/contracts/UniswapV2.sol @@ -1,25 +1,23 @@ pragma solidity 0.5.12; + import "./interfaces/IUniswapV2.sol"; + import "./libraries/Math.sol"; -import "./libraries/SafeMath128.sol"; -import "./libraries/UQ104x104.sol"; +import "./libraries/UQ128x128.sol"; + import "./token/ERC20.sol"; import "./token/SafeTransfer.sol"; contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTransfer { - using SafeMath128 for uint128; using SafeMath for uint; - using UQ104x104 for uint240; + using UQ128x128 for uint; address public factory; - address public token0; - address public token1; - uint128 public reserve0; - uint128 public reserve1; - uint240 public priceCumulative0; - uint16 public blockNumberHalf0; - uint240 public priceCumulative1; - uint16 public blockNumberHalf1; + address public token0; address public token1; + + uint128 public reserve0; uint128 public reserve1; + uint public priceCumulative0; uint public priceCumulative1; + uint64 public priceCumulative0Overflow; uint64 public priceCumulative1Overflow; uint64 public blockNumber; bool private notEntered = true; modifier lock() { @@ -30,30 +28,21 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran } event LiquidityMinted( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, uint liquidity ); event LiquidityBurned( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve1, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, uint liquidity ); event Swap( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve1, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, address input ); @@ -64,16 +53,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran function initialize(address _token0, address _token1) external { require(token0 == address(0) && token1 == address(0), 'UniswapV2: ALREADY_INITIALIZED'); - token0 = _token0; - token1 = _token1; - } - - function getReserves() external view returns (uint128, uint128) { - return (reserve0, reserve1); - } - - function readOracleBlockNumber() public view returns (uint32) { - return (uint32(blockNumberHalf0) << 16) + blockNumberHalf1; + (token0, token1) = (_token0, _token1); } // uniswap-v1 naming @@ -86,30 +66,19 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran } function update(uint balance0, uint balance1) private { - uint32 blockNumberLast = readOracleBlockNumber(); - - // if any blocks have gone by since the last time this function was called, we have to update - if (block.number > blockNumberLast) { - // we have to ensure that neither reserves are 0, else our price division fails + if (block.number > blockNumber) { if (reserve0 != 0 && reserve1 != 0) { - // get the prices according to the reserves as of the last official interaction with the contract - uint240 price0 = UQ104x104.encode(reserve0).qdiv(reserve1); - uint240 price1 = UQ104x104.encode(reserve1).qdiv(reserve0); - - // multiply these prices by the number of elapsed blocks and add to the accumulators - uint32 blocksElapsed = block.number.downcast32() - blockNumberLast; - priceCumulative0 += price0 * blocksElapsed; - priceCumulative1 += price1 * blocksElapsed; + uint64 blocksElapsed = uint64(block.number) - blockNumber; // doesn't overflow until >the end of time + (uint p0, uint64 po0) = Math.mul512(UQ128x128.encode(reserve0).qdiv(reserve1), blocksElapsed); + (uint p1, uint64 po1) = Math.mul512(UQ128x128.encode(reserve1).qdiv(reserve0), blocksElapsed); + uint64 pc0o; uint64 pc1o; + (priceCumulative0, pc0o) = Math.add512(priceCumulative0, priceCumulative0Overflow, p0, po0); + (priceCumulative1, pc1o) = Math.add512(priceCumulative1, priceCumulative1Overflow, p1, po1); + (priceCumulative0Overflow, priceCumulative1Overflow) = (pc0o, pc1o); } - - // update the last block number - blockNumberHalf0 = uint16(block.number >> 16); - blockNumberHalf1 = uint16(block.number); + blockNumber = uint64(block.number); // doesn't overflow until >the end of time } - - // update reserves - reserve0 = balance0.clamp128(); - reserve1 = balance1.clamp128(); + (reserve0, reserve1) = (balance0.clamp128(), balance1.clamp128()); // update reserves } function mintLiquidity(address recipient) external lock returns (uint liquidity) { @@ -123,6 +92,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran Math.min(amount0.mul(totalSupply) / reserve0, amount1.mul(totalSupply) / reserve1); require(liquidity > 0, "UniswapV2: INSUFFICIENT_VALUE"); mint(recipient, liquidity); + update(balance0, balance1); emit LiquidityMinted(msg.sender, recipient, amount0, amount1, reserve0, reserve1, liquidity); } @@ -130,60 +100,73 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran function burnLiquidity(address recipient) external lock returns (uint amount0, uint amount1) { uint liquidity = balanceOf[address(this)]; require(liquidity > 0, "UniswapV2: INSUFFICIENT_VALUE"); + amount0 = liquidity.mul(reserve0) / totalSupply; amount1 = liquidity.mul(reserve1) / totalSupply; require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE"); safeTransfer(token0, recipient, amount0); safeTransfer(token1, recipient, amount1); + update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); emit LiquidityBurned(msg.sender, recipient, amount0, amount1, reserve0, reserve1, liquidity); } function swap0(address recipient) external lock returns (uint amount1) { uint balance0 = IERC20(token0).balanceOf(address(this)); - uint amount0 = balance0.sub(reserve0); // this can fail + uint amount0 = balance0.sub(reserve0); // this can fail for weird tokens, hence sync + require(amount0 > 0, "UniswapV2: INSUFFICIENT_VALUE_INPUT"); amount1 = getInputPrice(amount0, reserve0, reserve1); require(amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE_OUTPUT"); safeTransfer(token1, recipient, amount1); + update(balance0, IERC20(token1).balanceOf(address(this))); emit Swap(msg.sender, recipient, amount0, amount1, reserve0, reserve1, token0); } function swap1(address recipient) external lock returns (uint amount0) { uint balance1 = IERC20(token1).balanceOf(address(this)); - uint amount1 = balance1.sub(reserve1); // this can fail + uint amount1 = balance1.sub(reserve1); // this can fail for weird tokens, hence sync + require(amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE_INPUT"); amount0 = getInputPrice(amount1, reserve1, reserve0); require(amount0 > 0, "UniswapV2: INSUFFICIENT_VALUE_OUTPUT"); safeTransfer(token0, recipient, amount0); + update(IERC20(token0).balanceOf(address(this)), balance1); emit Swap(msg.sender, recipient, amount0, amount1, reserve0, reserve1, token1); } + // this function almost certainly never needs to be called, it's for weird token function sync() external lock { update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); } - // DONT CALL THIS FUNCTION UNLESS token0 IS PERMANENTLY BROKEN // TODO: counterfactual + // DONT CALL THIS FUNCTION UNLESS token0 IS PERMANENTLY BROKEN function unsafeRageQuit0(address recipient) external lock returns (uint amount1) { uint liquidity = balanceOf[address(this)]; require(liquidity > 0, "UniswapV2: INSUFFICIENT_VALUE"); + + uint amount0 = liquidity.mul(reserve0) / totalSupply; amount1 = liquidity.mul(reserve1) / totalSupply; - require(amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE"); + require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE"); safeTransfer(token1, recipient, amount1); - update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); + + update(IERC20(token0).balanceOf(address(this)).sub(amount0), IERC20(token1).balanceOf(address(this))); emit LiquidityBurned(msg.sender, recipient, 0, amount1, reserve0, reserve1, liquidity); } - // DONT CALL THIS FUNCTION UNLESS token1 IS PERMANENTLY BROKEN // TODO: counterfactual + // DONT CALL THIS FUNCTION UNLESS token1 IS PERMANENTLY BROKEN function unsafeRageQuit1(address recipient) external lock returns (uint amount0) { uint liquidity = balanceOf[address(this)]; require(liquidity > 0, "UniswapV2: INSUFFICIENT_VALUE"); + amount0 = liquidity.mul(reserve0) / totalSupply; - require(amount0 > 0, "UniswapV2: INSUFFICIENT_VALUE"); + uint amount1 = liquidity.mul(reserve1) / totalSupply; + require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE"); safeTransfer(token0, recipient, amount0); - update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); + + update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)).sub(amount1)); emit LiquidityBurned(msg.sender, recipient, amount0, 0, reserve0, reserve1, liquidity); } } diff --git a/contracts/interfaces/IUniswapV2.sol b/contracts/interfaces/IUniswapV2.sol index cecf407..cc9725f 100644 --- a/contracts/interfaces/IUniswapV2.sol +++ b/contracts/interfaces/IUniswapV2.sol @@ -2,30 +2,21 @@ pragma solidity 0.5.12; interface IUniswapV2 { event LiquidityMinted( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve1, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, uint liquidity ); event LiquidityBurned( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve1, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, uint liquidity ); event Swap( - address indexed sender, - address indexed recipient, - uint amount0, - uint amount1, - uint128 reserve0, - uint128 reserve1, + address indexed sender, address indexed recipient, + uint amount0, uint amount1, + uint128 reserve0, uint128 reserve1, address input ); @@ -33,21 +24,20 @@ interface IUniswapV2 { function token0() external view returns (address); function token1() external view returns (address); - function priceCumulative0() external view returns (uint240); - function blockNumberHalf0() external view returns (uint16); - function priceCumulative1() external view returns (uint240); - function blockNumberHalf1() external view returns (uint16); + function reserve0() external view returns (uint128); + function reserve1() external view returns (uint128); - function getReserves() external view returns (uint128, uint128); - function readOracleBlockNumber() external view returns (uint32); + function priceCumulative0() external view returns (uint); + function priceCumulative1() external view returns (uint); + function priceCumulative0Overflow() external view returns (uint64); + function priceCumulative1Overflow() external view returns (uint64); + function blockNumber() external view returns (uint64); function getInputPrice(uint inputAmount, uint inputReserve, uint outputReserve) external pure returns (uint); function mintLiquidity(address recipient) external returns (uint liquidity); function burnLiquidity(address recipient) external returns (uint amount0, uint amount1); - function unsafeRageQuit0(address recipient) external returns (uint amountToken1); - function unsafeRageQuit1(address recipient) external returns (uint amountToken0); - function swap0(address recipient) external returns (uint amountToken1); - function swap1(address recipient) external returns (uint amountToken0); + function swap0(address recipient) external returns (uint amount1); + function swap1(address recipient) external returns (uint amount0); function sync() external; } diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index 5350d3d..eb1a846 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -1,6 +1,20 @@ pragma solidity 0.5.12; library Math { + function add512(uint x0, uint64 x1, uint y0, uint64 y1) internal pure returns (uint z0, uint64 z1) { + assembly { + z0 := add(x0, y0) + z1 := add(add(x1, y1), lt(z0, x0)) + } + } + function mul512(uint x, uint64 y) internal pure returns (uint z0, uint64 z1) { + assembly { + let mm := mulmod(x, y, not(0)) + z0 := mul(x, y) + z1 := sub(sub(mm, z0), lt(mm, z0)) + } + } + function min(uint x, uint y) internal pure returns (uint z) { return x <= y ? x : y; } diff --git a/contracts/libraries/SafeMath.sol b/contracts/libraries/SafeMath.sol index 4280755..5b59311 100644 --- a/contracts/libraries/SafeMath.sol +++ b/contracts/libraries/SafeMath.sol @@ -14,14 +14,4 @@ library SafeMath { function clamp128(uint y) internal pure returns (uint128 z) { z = y <= uint128(-1) ? uint128(y) : uint128(-1); } - - function downcast128(uint y) internal pure returns (uint128 z) { - require(y <= uint128(-1), "downcast-128-overflow"); - z = uint128(y); - } - - function downcast32(uint y) internal pure returns (uint32 z) { - require(y <= uint32(-1), "downcast-32-overflow"); - z = uint32(y); - } } diff --git a/contracts/libraries/SafeMath128.sol b/contracts/libraries/SafeMath128.sol deleted file mode 100644 index cdff376..0000000 --- a/contracts/libraries/SafeMath128.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity 0.5.12; - -library SafeMath128 { - function add(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x + y) >= x, "ds-math-add-overflow"); - } - function sub(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x - y) <= x, "ds-math-sub-underflow"); - } - function mul(uint128 x, uint128 y) internal pure returns (uint128 z) { - require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); - } -} diff --git a/contracts/libraries/UQ104x104.sol b/contracts/libraries/UQ104x104.sol deleted file mode 100644 index 3e4f449..0000000 --- a/contracts/libraries/UQ104x104.sol +++ /dev/null @@ -1,33 +0,0 @@ -pragma solidity 0.5.12; - -// helpful links -// https://en.wikipedia.org/wiki/Q_(number_format) -// https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.md -// https://github.com/gnosis/solidity-arithmetic - -library UQ104x104 { - uint240 constant Q104 = 2**104; - - // we want to encode a uint128 `y` s.t. `y := y_encoded / 2**104` (i.e. with a Q104 denominator). - // in other words, to encode `y` we simply multiply by `2**104`, aka Q104. - // in the case of a traditional UQ104.104, we'd store this output in a 208-bit slot, - // but since we're encoding a uint128, this would overflow for values of `y` in (`uint104(-1)`, `uint128(-1)`], - // so instead we need to store the output in at least 232 bits (we use 240 for compatibility later on) - function encode(uint128 y) internal pure returns (uint240 z) { - return uint240(y) * Q104; - } - - // we want to divide a modified UQ104.104 (the output of encode above) by an unencoded uint128 and return another - // modified UQ104.104. to do this, it's sufficient to divide the UQ104.104 by the unencoded value. - // since we want our output to fit in 208 bits, and behave consistently at the margins, we clamp this quotient - // within [1, uint208(-1)] - function qdiv(uint240 x, uint128 y) internal pure returns (uint240 z) { - z = x / y; - - if (z == 0) { - z = 1; - } else if (z > uint208(-1)) { - z = uint208(-1); - } - } -} diff --git a/contracts/libraries/UQ128x128.sol b/contracts/libraries/UQ128x128.sol new file mode 100644 index 0000000..9a3ea7e --- /dev/null +++ b/contracts/libraries/UQ128x128.sol @@ -0,0 +1,22 @@ +pragma solidity 0.5.12; + +// helpful links +// https://en.wikipedia.org/wiki/Q_(number_format) +// https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.md +// https://github.com/gnosis/solidity-arithmetic + +library UQ128x128 { + uint constant Q128 = 2**128; + + // we want to encode a uint128 `y` s.t. `y := y_encoded / 2**128` (i.e. with a Q128 denominator). + // in other words, to encode `y` we simply multiply by `2**128`, aka Q104, and store this in a 208-bit slot. + function encode(uint128 y) internal pure returns (uint z) { + return uint(y) * Q128; // guaranteed not to overflow + } + + // we want to divide a UQ128.128 (the output of encode above) by an unencoded uint128 and return another + // modified UQ128.128. to do this, it's sufficient to divide the UQ128.128 by the unencoded value. + function qdiv(uint x, uint128 y) internal pure returns (uint z) { + z = x / y; + } +} diff --git a/contracts/token/SafeTransfer.sol b/contracts/token/SafeTransfer.sol index 24ea313..f29af94 100644 --- a/contracts/token/SafeTransfer.sol +++ b/contracts/token/SafeTransfer.sol @@ -2,7 +2,7 @@ pragma solidity 0.5.12; contract SafeTransfer { function safeTransfer(address token, address to, uint value) internal { - (bool success, bytes memory data) = token.call(abi.encodeWithSignature("transfer(address,uint)", to, value)); + (bool success, bytes memory data) = token.call(abi.encodeWithSignature("transfer(address,uint256)", to, value)); require(success, "SafeTransfer: SWAP_REVERTED"); diff --git a/test/UniswapV2.spec.ts b/test/UniswapV2.spec.ts index 212d316..09e4a29 100644 --- a/test/UniswapV2.spec.ts +++ b/test/UniswapV2.spec.ts @@ -27,7 +27,7 @@ describe('UniswapV2', () => { exchange = _exchange }) - it('getAmountOutput', async () => { + it('getInputPrice', async () => { const testCases: BigNumber[][] = [ [1, 5, 10], [1, 10, 5], @@ -52,7 +52,7 @@ describe('UniswapV2', () => { '0996006981039903216' ].map((n: string) => bigNumberify(n)) - const outputs = await Promise.all(testCases.map(a => exchange.getAmountOutput(...a))) + const outputs = await Promise.all(testCases.map(a => exchange.getInputPrice(...a))) expect(outputs).to.deep.eq(expectedOutputs) }) @@ -62,6 +62,9 @@ describe('UniswapV2', () => { const token1Amount = expandTo18Decimals(4) const expectedLiquidity = expandTo18Decimals(2) + expect(await exchange.reserve0()).to.eq(bigNumberify(0)) + expect(await exchange.reserve1()).to.eq(bigNumberify(0)) + await token0.transfer(exchange.address, token0Amount) await token1.transfer(exchange.address, token1Amount) await expect(exchange.connect(wallet).mintLiquidity(wallet.address)) @@ -78,6 +81,9 @@ describe('UniswapV2', () => { expect(await exchange.totalSupply()).to.eq(expectedLiquidity) expect(await exchange.balanceOf(wallet.address)).to.eq(expectedLiquidity) + + expect(await exchange.reserve0()).to.eq(token0Amount) + expect(await exchange.reserve1()).to.eq(token1Amount) }) async function addLiquidity(token0Amount: BigNumber, token1Amount: BigNumber) { @@ -93,23 +99,10 @@ describe('UniswapV2', () => { // const swapAmount = expandTo18Decimals(1) // await token0.transfer(exchange.address, swapAmount) - // await exchange.connect(wallet).swap(token0.address, wallet.address) + // await exchange.connect(wallet).swap0(wallet.address) // await token0.transfer(exchange.address, swapAmount) - // console.log((await exchange.estimate.swap(token0.address, wallet.address)).toString()) - // }) - - // it('swap:gas', async () => { - // const token0Amount = expandTo18Decimals(10) - // const token1Amount = expandTo18Decimals(5) - // await addLiquidity(token0Amount, token1Amount) - - // const swapAmount = expandTo18Decimals(1) - // await token1.transfer(exchange.address, swapAmount) - // await exchange.connect(wallet).swap(token1.address, wallet.address) - - // await token1.transfer(exchange.address, swapAmount) - // console.log((await exchange.estimate.swap(token1.address, wallet.address)).toString()) + // console.log((await exchange.estimate.swap0(wallet.address)).toString()) // }) it('swap', async () => { @@ -120,8 +113,7 @@ describe('UniswapV2', () => { const swapAmount = expandTo18Decimals(1) const expectedOutputAmount = bigNumberify('1662497915624478906') await token0.transfer(exchange.address, swapAmount) - expect(await exchange.swap) - await expect(exchange.connect(wallet).swap(token0.address, wallet.address)) + await expect(exchange.connect(wallet).swap0(wallet.address)) .to.emit(exchange, 'Swap') .withArgs( wallet.address, @@ -136,7 +128,9 @@ describe('UniswapV2', () => { expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount.add(swapAmount)) expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.sub(expectedOutputAmount)) + const totalSupplyToken0 = await token0.totalSupply() const totalSupplyToken1 = await token1.totalSupply() + expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0.sub(token0Amount).sub(swapAmount)) expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1.sub(token1Amount).add(expectedOutputAmount)) }) @@ -161,38 +155,4 @@ describe('UniswapV2', () => { expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0) expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1) }) - - it('getReserves', async () => { - expect(await exchange.getReserves()).to.deep.eq([0, 0].map(n => bigNumberify(n))) - - const token0Amount = expandTo18Decimals(3) - const token1Amount = expandTo18Decimals(3) - await addLiquidity(token0Amount, token1Amount) - - expect(await exchange.getReserves()).to.deep.eq([token0Amount, token1Amount]) - }) - - // it('getReservesCumulative', async () => { - // await expect(exchange.getReservesCumulative()).to.be.revertedWith('UniswapV2: NOT_INITIALIZED') - - // const token0Amount = expandTo18Decimals(3) - // const token1Amount = expandTo18Decimals(3) - // await addLiquidity(token0Amount, token1Amount) - - // expect(await exchange.getReservesCumulative()).to.deep.eq( - // [0, 0, 0, 0].map((n: number): BigNumber => bigNumberify(n)) - // ) - - // const dummySwapAmount = bigNumberify(1) - // await token0.transfer(exchange.address, dummySwapAmount) - // await exchange.connect(wallet).swap(token0.address, wallet.address) - - // const reservesCumulativePost = await exchange.getReservesCumulative() - // expect(reservesCumulativePost).to.deep.eq([ - // token0Amount.mul(bigNumberify(2)), - // token1Amount.mul(bigNumberify(2)), - // bigNumberify(0), - // bigNumberify(0) - // ]) - // }) })