diff --git a/README.md b/README.md index dbb1ffa..3b815b6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,5 @@ yarn test - [dapphub math](https://github.com/dapphub/ds-math/blob/de4576712dcf2c5152d16a04e677002d51d46e60/src/math.sol) - [dapp-bin math](https://github.com/ethereum/dapp-bin/pull/50) - [OpenZeppelin ECDSA](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/81b1e4810761b088922dbd19a0642873ea581176/contracts/cryptography/ECDSA.sol) +- [OpenZeppelin SafeERC20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/81b1e4810761b088922dbd19a0642873ea581176/contracts/token/ERC20/SafeERC20.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 b05770c..c0923eb 100644 --- a/contracts/UniswapV2.sol +++ b/contracts/UniswapV2.sol @@ -2,25 +2,23 @@ pragma solidity 0.5.13; import "./interfaces/IUniswapV2.sol"; import "./libraries/Math.sol"; -import "./libraries/UQ128x128.sol"; +import "./libraries/UQ112x112.sol"; import "./token/ERC20.sol"; import "./token/SafeTransfer.sol"; contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTransfer { using SafeMath for uint; - using UQ128x128 for uint; + using UQ112x112 for uint224; address public factory; address public token0; address public token1; - uint128 public reserve0; - uint128 public reserve1; + uint112 public reserve0; + uint112 public reserve1; + uint32 public blockNumberLast; uint public priceCumulative0; uint public priceCumulative1; - uint64 public priceCumulative0Overflow; - uint64 public priceCumulative1Overflow; - uint64 public blockNumber; uint private invariantLast; @@ -67,7 +65,8 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran function initialize(address _token0, address _token1) external { require(msg.sender == factory && token0 == address(0) && token1 == address(0), 'UniswapV2: FORBIDDEN'); - (token0, token1) = (_token0, _token1); + token0 = _token0; + token1 = _token1; } function getInputPrice(uint inputAmount, uint inputReserve, uint outputReserve) public pure returns (uint) { @@ -79,19 +78,17 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran } function update(uint balance0, uint balance1) private { - if (block.number > blockNumber) { + uint32 blockNumberModulo = uint32(block.number % 2**32); + uint32 blocksElapsed = blockNumberModulo - blockNumberLast; // overflow is desired + if (blocksElapsed > 0) { if (reserve0 != 0 && reserve1 != 0) { - uint blocksElapsed = block.number - blockNumber; - (uint p0, uint po0) = Math.mul512(UQ128x128.encode(reserve0).qdiv(reserve1), blocksElapsed); - (uint p1, uint po1) = Math.mul512(UQ128x128.encode(reserve1).qdiv(reserve0), blocksElapsed); - uint pc0o; uint pc1o; - (priceCumulative0, pc0o) = Math.add512(priceCumulative0, priceCumulative0Overflow, p0, po0); - (priceCumulative1, pc1o) = Math.add512(priceCumulative1, priceCumulative1Overflow, p1, po1); - (priceCumulative0Overflow, priceCumulative1Overflow) = (uint64(pc0o), uint64(pc1o)); + uint224 price0 = UQ112x112.encode(reserve0).qdiv(reserve1); + uint224 price1 = UQ112x112.encode(reserve1).qdiv(reserve0); + priceCumulative0 += uint256(price0) * blocksElapsed; // * never overflows, + overflow is desired + priceCumulative1 += uint256(price1) * blocksElapsed; // * never overflows, + overflow is desired } - blockNumber = uint64(block.number); // doesn't overflow until >the end of time } - (reserve0, reserve1) = (balance0.clamp128(), balance1.clamp128()); // update reserves + (reserve0, reserve1, blockNumberLast) = (balance0.clamp112(), balance1.clamp112(), blockNumberModulo); } // mint liquidity equivalent to 20% of accumulated fees @@ -101,7 +98,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran uint numerator = totalSupply.mul(invariant.sub(invariantLast)); uint denominator = uint256(4).mul(invariant).add(invariantLast); uint liquidity = numerator / denominator; - mint(factory, liquidity); + _mint(factory, liquidity); // factory is just a placeholder emit FeesMinted(liquidity); } } @@ -109,7 +106,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran function mintLiquidity(address recipient) external lock returns (uint liquidity) { uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); - require(balance0 <= uint112(-1) && balance1 <= uint112(-1), "UniswapV2: EXCESSSIVE_LIQUIDITY"); + require(balance0 <= uint112(-1) && balance1 <= uint112(-1), "UniswapV2: EXCESS_LIQUIDITY"); uint amount0 = balance0.sub(reserve0); uint amount1 = balance1.sub(reserve1); @@ -118,7 +115,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran Math.sqrt(amount0.mul(amount1)) : Math.min(amount0.mul(totalSupply) / reserve0, amount1.mul(totalSupply) / reserve1); require(liquidity > 0, "UniswapV2: INSUFFICIENT_VALUE"); - mint(recipient, liquidity); + _mint(recipient, liquidity); update(balance0, balance1); invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); @@ -127,21 +124,25 @@ 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)]; + uint balance0 = IERC20(token0).balanceOf(address(this)); + uint balance1 = IERC20(token1).balanceOf(address(this)); mintFees(); - amount0 = liquidity.mul(reserve0) / totalSupply; - amount1 = liquidity.mul(reserve1) / totalSupply; + amount0 = liquidity.mul(balance0) / totalSupply; // intentionally using balances not reserves + amount1 = liquidity.mul(balance1) / totalSupply; // intentionally using balances not reserves require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_VALUE"); safeTransfer(token0, recipient, amount0); safeTransfer(token1, recipient, amount1); + _burn(address(this), liquidity); - update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); + update(balance0, balance1); invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); 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)); + require(balance0 <= uint112(-1), "UniswapV2: EXCESS_BALANCE"); uint amount0 = balance0.sub(reserve0); amount1 = getInputPrice(amount0, reserve0, reserve1); @@ -154,6 +155,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTran function swap1(address recipient) external lock returns (uint amount0) { uint balance1 = IERC20(token1).balanceOf(address(this)); + require(balance1 <= uint112(-1), "UniswapV2: EXCESS_BALANCE"); uint amount1 = balance1.sub(reserve1); amount0 = getInputPrice(amount1, reserve1, reserve0); diff --git a/contracts/interfaces/IUniswapV2.sol b/contracts/interfaces/IUniswapV2.sol index 7110247..840373a 100644 --- a/contracts/interfaces/IUniswapV2.sol +++ b/contracts/interfaces/IUniswapV2.sol @@ -28,6 +28,7 @@ interface IUniswapV2 { uint128 reserve1, address input ); + event FeesMinted(uint liquidity); function factory() external view returns (address); function token0() external view returns (address); @@ -47,5 +48,7 @@ interface IUniswapV2 { function burnLiquidity(address recipient) external returns (uint amount0, uint amount1); function swap0(address recipient) external returns (uint amount1); function swap1(address recipient) external returns (uint amount0); + function sync() external; + function sweep() external; } diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index bca3dda..b4a557d 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -1,20 +1,6 @@ pragma solidity 0.5.13; library Math { - function add512(uint x0, uint x1, uint y0, uint y1) internal pure returns (uint z0, uint z1) { - assembly { // solium-disable-line security/no-inline-assembly - z0 := add(x0, y0) - z1 := add(add(x1, y1), lt(z0, x0)) - } - } - function mul512(uint x, uint y) internal pure returns (uint z0, uint z1) { - assembly { // solium-disable-line security/no-inline-assembly - z0 := mul(x, y) - let mm := mulmod(x, y, not(0)) - 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 ab3d7b3..6548683 100644 --- a/contracts/libraries/SafeMath.sol +++ b/contracts/libraries/SafeMath.sol @@ -11,7 +11,7 @@ library SafeMath { require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); } - function clamp128(uint y) internal pure returns (uint128 z) { - z = y <= uint128(-1) ? uint128(y) : uint128(-1); + function clamp112(uint y) internal pure returns (uint112 z) { + z = y <= uint112(-1) ? uint112(y) : uint112(-1); } } diff --git a/contracts/libraries/UQ112x112.sol b/contracts/libraries/UQ112x112.sol new file mode 100644 index 0000000..80220ff --- /dev/null +++ b/contracts/libraries/UQ112x112.sol @@ -0,0 +1,15 @@ +pragma solidity 0.5.13; + +library UQ112x112 { + uint224 constant Q112 = 2**112; + + // encode a uint112 as a UQ112.112 fixed point number s.t. `y := y_encoded / 2**112` (i.e. with a Q112 denominator). + function encode(uint112 y) internal pure returns (uint224 z) { + z = uint224(y) * Q112; // never overflows + } + + // divide a UQ112.112 by a uint112 and return the result as a UQ112.112 + function qdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { + z = x / y; + } +} diff --git a/contracts/libraries/UQ128x128.sol b/contracts/libraries/UQ128x128.sol deleted file mode 100644 index 6b2c9cb..0000000 --- a/contracts/libraries/UQ128x128.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity 0.5.13; - -// 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/ERC20.sol b/contracts/token/ERC20.sol index d0d7727..76c9e31 100644 --- a/contracts/token/ERC20.sol +++ b/contracts/token/ERC20.sol @@ -30,7 +30,7 @@ contract ERC20 is IERC20 { symbol = _symbol; decimals = _decimals; if (_totalSupply > 0) { - mint(msg.sender, _totalSupply); + _mint(msg.sender, _totalSupply); } DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), @@ -41,7 +41,7 @@ contract ERC20 is IERC20 { )); } - function mint(address to, uint value) internal { + function _mint(address to, uint value) internal { totalSupply = totalSupply.add(value); balanceOf[to] = balanceOf[to].add(value); emit Transfer(address(0), to, value);