improve fee logic, make clamping logic more robust
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
15
contracts/libraries/UQ112x112.sol
Normal file
15
contracts/libraries/UQ112x112.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user