From faf0826a29ce5e95585ce97bdddbb18ef5326d5d Mon Sep 17 00:00:00 2001 From: mingda Date: Sat, 25 Jul 2020 01:19:26 +0800 Subject: [PATCH] Public Arbitrageur between UniswapV2 and DODO --- contracts/helper/UniswapArbitrageur.sol | 176 +++++++ contracts/helper/UniswapV2.sol | 636 ++++++++++++++++++++++++ contracts/intf/IDODO.sol | 4 + 3 files changed, 816 insertions(+) create mode 100644 contracts/helper/UniswapArbitrageur.sol create mode 100644 contracts/helper/UniswapV2.sol diff --git a/contracts/helper/UniswapArbitrageur.sol b/contracts/helper/UniswapArbitrageur.sol new file mode 100644 index 0000000..35460a9 --- /dev/null +++ b/contracts/helper/UniswapArbitrageur.sol @@ -0,0 +1,176 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +import {Ownable} from "../lib/Ownable.sol"; +import {IDODO} from "../intf/IDODO.sol"; +import {IERC20} from "../intf/IERC20.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +interface IUniswapV2Pair { + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; +} + +contract UniswapArbitrageur { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + bool public _INITIALIZED_; + + address public _UNISWAP_; + address public _DODO_; + address public _BASE_; + address public _QUOTE_; + + bool public _REVERSE_; // true if dodo.baseToken=uniswap.token0 + + uint256 public lastArbitrageProfit; + + function init(address _uniswap, address _dodo) external { + require(!_INITIALIZED_, "ALREADY_INITIALIZED"); + _INITIALIZED_ = true; + _UNISWAP_ = _uniswap; + _DODO_ = _dodo; + + _BASE_ = IDODO(_DODO_)._BASE_TOKEN_(); + _QUOTE_ = IDODO(_DODO_)._QUOTE_TOKEN_(); + + address token0 = IUniswapV2Pair(_UNISWAP_).token0(); + address token1 = IUniswapV2Pair(_UNISWAP_).token1(); + + if (token0 == _BASE_ && token1 == _QUOTE_) { + _REVERSE_ = false; + } else if (token0 == _QUOTE_ && token1 == _BASE_) { + _REVERSE_ = true; + } else { + require(true, "DODO_UNISWAP_NOT_MATCH"); + } + } + + function executeBuyArbitrage(uint256 baseAmount) external returns (uint256 quoteProfit) { + IDODO(_DODO_).buyBaseToken(baseAmount, uint256(-1), "0xd"); + return lastArbitrageProfit; + } + + function executeSellArbitrage(uint256 baseAmount) external returns (uint256 baseProfit) { + IDODO(_DODO_).sellBaseToken(baseAmount, 0, "0xd"); + return lastArbitrageProfit; + } + + function dodoCall( + bool isDODOBuy, + uint256 baseAmount, + uint256 quoteAmount, + bytes calldata + ) external { + require(msg.sender == _DODO_, "WRONG_DODO"); + if (_REVERSE_) { + _inverseArbitrage(isDODOBuy, baseAmount, quoteAmount); + } else { + _arbitrage(isDODOBuy, baseAmount, quoteAmount); + } + } + + function _inverseArbitrage( + bool isDODOBuy, + uint256 baseAmount, + uint256 quoteAmount + ) internal { + (uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves(); + uint256 token0Balance = uint256(_reserve0); + uint256 token1Balance = uint256(_reserve1); + uint256 token0Amount; + uint256 token1Amount; + if (isDODOBuy) { + IERC20(_BASE_).transfer(_UNISWAP_, baseAmount); + // transfer token1 into uniswap + uint256 newToken0Balance = token0Balance.mul(token1Balance).div( + token1Balance.add(baseAmount) + ); + token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969 + require(token0Amount > quoteAmount, "NOT_PROFITABLE"); + lastArbitrageProfit = token0Amount.sub(quoteAmount); + IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); + IERC20(_QUOTE_).transfer(tx.origin, lastArbitrageProfit); + } else { + IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount); + // transfer token0 into uniswap + uint256 newToken1Balance = token0Balance.mul(token1Balance).div( + token0Balance.add(quoteAmount) + ); + token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969 + require(token1Amount > baseAmount, "NOT_PROFITABLE"); + lastArbitrageProfit = token1Amount.sub(baseAmount); + IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); + IERC20(_BASE_).transfer(tx.origin, lastArbitrageProfit); + } + } + + function _arbitrage( + bool isDODOBuy, + uint256 baseAmount, + uint256 quoteAmount + ) internal { + (uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves(); + uint256 token0Balance = uint256(_reserve0); + uint256 token1Balance = uint256(_reserve1); + uint256 token0Amount; + uint256 token1Amount; + if (isDODOBuy) { + IERC20(_BASE_).transfer(_UNISWAP_, baseAmount); + // transfer token0 into uniswap + uint256 newToken1Balance = token1Balance.mul(token0Balance).div( + token0Balance.add(baseAmount) + ); + token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969 + require(token1Amount > quoteAmount, "NOT_PROFITABLE"); + lastArbitrageProfit = token1Amount.sub(quoteAmount); + IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); + IERC20(_QUOTE_).transfer(tx.origin, lastArbitrageProfit); + } else { + IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount); + // transfer token1 into uniswap + uint256 newToken0Balance = token1Balance.mul(token0Balance).div( + token1Balance.add(quoteAmount) + ); + token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969 + require(token0Amount > baseAmount, "NOT_PROFITABLE"); + lastArbitrageProfit = token0Amount.sub(baseAmount); + IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); + IERC20(_BASE_).transfer(tx.origin, lastArbitrageProfit); + } + } + + function retrieve(address token, uint256 amount) external { + IERC20(token).safeTransfer(msg.sender, amount); + } + + function approve(address token, address spender) external { + IERC20(token).approve(spender, uint256(-1)); + } +} diff --git a/contracts/helper/UniswapV2.sol b/contracts/helper/UniswapV2.sol new file mode 100644 index 0000000..282d07b --- /dev/null +++ b/contracts/helper/UniswapV2.sol @@ -0,0 +1,636 @@ +/** + *Submitted for verification at Etherscan.io on 2020-05-05 + */ + +// File: contracts/interfaces/IUniswapV2Pair.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} + +// File: contracts/interfaces/IUniswapV2ERC20.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2ERC20 { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} + +// File: contracts/libraries/SafeMath.sol + +pragma solidity 0.6.9; + +// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) + +library SafeMath { + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } +} + +// File: contracts/UniswapV2ERC20.sol + +pragma solidity 0.6.9; + +contract UniswapV2ERC20 { + using SafeMath for uint256; + + string public constant name = "Uniswap V2"; + string public constant symbol = "UNI-V2"; + uint8 public constant decimals = 18; + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + bytes32 public DOMAIN_SEPARATOR; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + mapping(address => uint256) public nonces; + + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function _mint(address to, uint256 value) internal { + totalSupply = totalSupply.add(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(address(0), to, value); + } + + function _burn(address from, uint256 value) internal { + balanceOf[from] = balanceOf[from].sub(value); + totalSupply = totalSupply.sub(value); + emit Transfer(from, address(0), value); + } + + function _approve( + address owner, + address spender, + uint256 value + ) private { + allowance[owner][spender] = value; + emit Approval(owner, spender, value); + } + + function _transfer( + address from, + address to, + uint256 value + ) private { + balanceOf[from] = balanceOf[from].sub(value); + balanceOf[to] = balanceOf[to].add(value); + emit Transfer(from, to, value); + } + + function approve(address spender, uint256 value) external returns (bool) { + _approve(msg.sender, spender, value); + return true; + } + + function transfer(address to, uint256 value) external returns (bool) { + _transfer(msg.sender, to, value); + return true; + } + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool) { + if (allowance[from][msg.sender] != uint256(-1)) { + allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); + } + _transfer(from, to, value); + return true; + } + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(deadline >= block.timestamp, "UniswapV2: EXPIRED"); + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline) + ) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + require( + recoveredAddress != address(0) && recoveredAddress == owner, + "UniswapV2: INVALID_SIGNATURE" + ); + _approve(owner, spender, value); + } +} + +// File: contracts/libraries/Math.sol + +pragma solidity 0.6.9; + +// a library for performing various math operations + +library Math { + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x < y ? x : y; + } + + // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint256 y) internal pure returns (uint256 z) { + if (y > 3) { + z = y; + uint256 x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } +} + +// File: contracts/libraries/UQ112x112.sol + +pragma solidity 0.6.9; + +// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) + +// range: [0, 2**112 - 1] +// resolution: 1 / 2**112 + +library UQ112x112 { + uint224 constant Q112 = 2**112; + + // encode a uint112 as a UQ112x112 + function encode(uint112 y) internal pure returns (uint224 z) { + z = uint224(y) * Q112; // never overflows + } + + // divide a UQ112x112 by a uint112, returning a UQ112x112 + function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { + z = x / uint224(y); + } +} + +// File: contracts/interfaces/IERC20.sol + +pragma solidity >=0.5.0; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); +} + +// File: contracts/interfaces/IUniswapV2Factory.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Factory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint256); + + function feeTo() external view returns (address); + + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + + function setFeeToSetter(address) external; +} + +// File: contracts/interfaces/IUniswapV2Callee.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Callee { + function uniswapV2Call( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; +} + +// File: contracts/UniswapV2Pair.sol + +pragma solidity 0.6.9; + +contract UniswapV2Pair is UniswapV2ERC20 { + using SafeMath for uint256; + using UQ112x112 for uint224; + + uint256 public constant MINIMUM_LIQUIDITY = 10**3; + bytes4 private constant SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)"))); + + address public factory; + address public token0; + address public token1; + + uint112 private reserve0; // uses single storage slot, accessible via getReserves + uint112 private reserve1; // uses single storage slot, accessible via getReserves + uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves + + uint256 public price0CumulativeLast; + uint256 public price1CumulativeLast; + uint256 public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event + + uint256 private unlocked = 1; + modifier lock() { + require(unlocked == 1, "UniswapV2: LOCKED"); + unlocked = 0; + _; + unlocked = 1; + } + + function getReserves() + public + view + returns ( + uint112 _reserve0, + uint112 _reserve1, + uint32 _blockTimestampLast + ) + { + _reserve0 = reserve0; + _reserve1 = reserve1; + _blockTimestampLast = blockTimestampLast; + } + + function _safeTransfer( + address token, + address to, + uint256 value + ) private { + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); + require( + success && (data.length == 0 || abi.decode(data, (bool))), + "UniswapV2: TRANSFER_FAILED" + ); + } + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 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; + token1 = _token1; + } + + // update reserves and, on the first call per block, price accumulators + function _update( + uint256 balance0, + uint256 balance1, + uint112 _reserve0, + uint112 _reserve1 + ) private { + require(balance0 <= uint112(-1) && balance1 <= uint112(-1), "UniswapV2: OVERFLOW"); + uint32 blockTimestamp = uint32(block.timestamp % 2**32); + uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired + if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { + // * never overflows, and + overflow is desired + price0CumulativeLast += + uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * + timeElapsed; + price1CumulativeLast += + uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * + timeElapsed; + } + reserve0 = uint112(balance0); + reserve1 = uint112(balance1); + blockTimestampLast = blockTimestamp; + emit Sync(reserve0, reserve1); + } + + // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) + function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { + address feeTo = IUniswapV2Factory(factory).feeTo(); + feeOn = feeTo != address(0); + uint256 _kLast = kLast; // gas savings + if (feeOn) { + if (_kLast != 0) { + uint256 rootK = Math.sqrt(uint256(_reserve0).mul(_reserve1)); + uint256 rootKLast = Math.sqrt(_kLast); + if (rootK > rootKLast) { + uint256 numerator = totalSupply.mul(rootK.sub(rootKLast)); + uint256 denominator = rootK.mul(5).add(rootKLast); + uint256 liquidity = numerator / denominator; + if (liquidity > 0) _mint(feeTo, liquidity); + } + } + } else if (_kLast != 0) { + kLast = 0; + } + } + + // this low-level function should be called from a contract which performs important safety checks + function mint(address to) external lock returns (uint256 liquidity) { + (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings + uint256 balance0 = IERC20(token0).balanceOf(address(this)); + uint256 balance1 = IERC20(token1).balanceOf(address(this)); + uint256 amount0 = balance0.sub(_reserve0); + uint256 amount1 = balance1.sub(_reserve1); + + bool feeOn = _mintFee(_reserve0, _reserve1); + uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee + if (_totalSupply == 0) { + liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); + _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens + } else { + liquidity = Math.min( + amount0.mul(_totalSupply) / _reserve0, + amount1.mul(_totalSupply) / _reserve1 + ); + } + require(liquidity > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED"); + _mint(to, liquidity); + + _update(balance0, balance1, _reserve0, _reserve1); + if (feeOn) kLast = uint256(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 (uint256 amount0, uint256 amount1) { + (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings + address _token0 = token0; // gas savings + address _token1 = token1; // gas savings + uint256 balance0 = IERC20(_token0).balanceOf(address(this)); + uint256 balance1 = IERC20(_token1).balanceOf(address(this)); + uint256 liquidity = balanceOf[address(this)]; + + bool feeOn = _mintFee(_reserve0, _reserve1); + uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee + amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution + amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution + require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED"); + _burn(address(this), liquidity); + _safeTransfer(_token0, to, amount0); + _safeTransfer(_token1, to, amount1); + balance0 = IERC20(_token0).balanceOf(address(this)); + balance1 = IERC20(_token1).balanceOf(address(this)); + + _update(balance0, balance1, _reserve0, _reserve1); + if (feeOn) kLast = uint256(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date + emit Burn(msg.sender, amount0, amount1, to); + } + + // this low-level function should be called from a contract which performs important safety checks + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external lock { + require(amount0Out > 0 || amount1Out > 0, "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"); + (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings + require( + amount0Out < _reserve0 && amount1Out < _reserve1, + "UniswapV2: INSUFFICIENT_LIQUIDITY" + ); + + uint256 balance0; + uint256 balance1; + { + // 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)); + } + uint256 amount0In = balance0 > _reserve0 - amount0Out + ? balance0 - (_reserve0 - amount0Out) + : 0; + uint256 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 + uint256 balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); + uint256 balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); + require( + balance0Adjusted.mul(balance1Adjusted) >= + uint256(_reserve0).mul(_reserve1).mul(1000**2), + "UniswapV2: K" + ); + } + + _update(balance0, balance1, _reserve0, _reserve1); + emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); + } + + // force balances to match reserves + function skim(address to) external lock { + address _token0 = token0; // gas savings + address _token1 = token1; // gas savings + _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); + _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); + } + + // force reserves to match balances + function sync() external lock { + _update( + IERC20(token0).balanceOf(address(this)), + IERC20(token1).balanceOf(address(this)), + reserve0, + reserve1 + ); + } +} diff --git a/contracts/intf/IDODO.sol b/contracts/intf/IDODO.sol index acbbea7..04ef9f0 100644 --- a/contracts/intf/IDODO.sol +++ b/contracts/intf/IDODO.sol @@ -56,4 +56,8 @@ interface IDODO { function _BASE_CAPITAL_TOKEN_() external returns (address); function _QUOTE_CAPITAL_TOKEN_() external returns (address); + + function _BASE_TOKEN_() external returns (address); + + function _QUOTE_TOKEN_() external returns (address); }