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:
@ -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
|
||||
|
||||
5
contracts/interfaces/IUniswapV2Callee.sol
Normal file
5
contracts/interfaces/IUniswapV2Callee.sol
Normal file
@ -0,0 +1,5 @@
|
||||
pragma solidity =0.5.16;
|
||||
|
||||
interface IUniswapV2Callee {
|
||||
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user