Skip to content

Commit

Permalink
don't pack prices as much
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahZinsmeister committed Dec 5, 2019
1 parent 607b1c6 commit b284b63
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 207 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
121 changes: 52 additions & 69 deletions contracts/UniswapV2.sol
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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
);

Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -123,67 +92,81 @@ 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);
}

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);
}
}
46 changes: 18 additions & 28 deletions contracts/interfaces/IUniswapV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,42 @@ 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
);

function factory() external view returns (address);
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;
}
14 changes: 14 additions & 0 deletions contracts/libraries/Math.sol
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down
10 changes: 0 additions & 10 deletions contracts/libraries/SafeMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
13 changes: 0 additions & 13 deletions contracts/libraries/SafeMath128.sol

This file was deleted.

33 changes: 0 additions & 33 deletions contracts/libraries/UQ104x104.sol

This file was deleted.

Loading

0 comments on commit b284b63

Please sign in to comment.