optimistic swaps (#53)

* alternative flash lending (renting) design

* add rent interface

* fix stack too deep error

rearrange order of k condition math

ignore erroneous out of gas errors in tests

* try removing rent in favor of monolithic swap

IUniswapV2Borrower -> IUniswapV2Callee

update tests

* fix implementation

* clean up math a bit

* amount{0,1}In -> amount{0,1}InNet

* charge on all inputs, not just net

* removed unnecessary safemath

* add to != token check

don't indent in scope

rename reserve{0,1}Next -> reserve{0,1}Adjusted

* > instead of >=

simplify algebra

reserve{0,1}Adjusted -> balance{0,1}Adjusted

add comments

* add some optimistic swap test cases
This commit is contained in:
Noah Zinsmeister
2020-02-17 15:06:43 -07:00
committed by GitHub
parent 3f5feaa369
commit b742e92502
5 changed files with 94 additions and 49 deletions

View File

@ -6,6 +6,7 @@ import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';
contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 {
using SafeMath for uint;
@ -49,13 +50,21 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 {
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(address indexed sender, address indexed tokenIn, uint amountIn, uint amountOut, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
constructor() public {
factory = msg.sender;
}
// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
@ -99,6 +108,7 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 {
}
}
// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
uint balance0 = IERC20(token0).balanceOf(address(this));
@ -118,10 +128,11 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 {
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
@ -142,40 +153,39 @@ contract UniswapV2Exchange is IUniswapV2Exchange, UniswapV2ERC20 {
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
function swap(address tokenIn, uint amountOut, address to) external lock {
require(amountOut > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
// this low-level function should be called from a contract which performs important safety checks
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
uint amountIn;
if (tokenIn == _token0) {
require(amountOut < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
balance0 = IERC20(_token0).balanceOf(address(this));
amountIn = balance0.sub(_reserve0);
require(amountIn > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
require(amountIn.mul(_reserve1 - amountOut).mul(997) >= amountOut.mul(_reserve0).mul(1000), 'UniswapV2: K');
_safeTransfer(_token1, to, amountOut);
balance1 = IERC20(_token1).balanceOf(address(this));
} else {
require(tokenIn == _token1, 'UniswapV2: INVALID_INPUT_TOKEN');
require(amountOut < _reserve0, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
balance1 = IERC20(_token1).balanceOf(address(this));
amountIn = balance1.sub(_reserve1);
require(amountIn > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
require(amountIn.mul(_reserve0 - amountOut).mul(997) >= amountOut.mul(_reserve1).mul(1000), 'UniswapV2: K');
_safeTransfer(_token0, to, amountOut);
balance0 = IERC20(_token0).balanceOf(address(this));
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, tokenIn, amountIn, amountOut, to);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves

View File

@ -0,0 +1,5 @@
pragma solidity =0.5.16;
interface IUniswapV2Callee {
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
}

View File

@ -23,7 +23,14 @@ interface IUniswapV2Exchange {
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(address indexed sender, address indexed tokenIn, uint amountIn, uint amountOut, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
@ -37,7 +44,7 @@ interface IUniswapV2Exchange {
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(address tokenIn, uint amountOut, address to) external;
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;