improve fee logic, make clamping logic more robust

This commit is contained in:
Noah Zinsmeister
2019-12-11 14:36:55 -05:00
parent fbcd002ed5
commit 8bdf549b98
8 changed files with 49 additions and 67 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);