From 8d0363c1ba7f054a52e5dfd45886ebaacde6985b Mon Sep 17 00:00:00 2001 From: Paul Barclay Date: Thu, 17 Jun 2021 08:17:21 -0700 Subject: [PATCH 1/2] Enable conversion on add/remove liquidity Enable QuickConverter to receive requests from approved contracts (not just tx.origin) Add converter.convert() to the end of addLiquidity, addLiquidityETH, and removeLiquidity --- contracts/QuickConverter02a.sol | 565 +++++++++++++++++++++++ contracts/UniswapV2Router02a.sol | 83 ++++ contracts/interfaces/IQuickConverter.sol | 5 + 3 files changed, 653 insertions(+) create mode 100755 contracts/QuickConverter02a.sol create mode 100644 contracts/UniswapV2Router02a.sol create mode 100644 contracts/interfaces/IQuickConverter.sol diff --git a/contracts/QuickConverter02a.sol b/contracts/QuickConverter02a.sol new file mode 100755 index 0000000..b6bee47 --- /dev/null +++ b/contracts/QuickConverter02a.sol @@ -0,0 +1,565 @@ +// File: contracts/libraries/SafeMath.sol + +pragma solidity =0.6.12; + +// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) + +library SafeMath { + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, 'ds-math-add-overflow'); + } + + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, 'ds-math-sub-underflow'); + } + + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); + } + + function div(uint a, uint b) internal pure returns (uint256) { + require(b > 0, "division by zero"); + return a / b; + } +} + +// File: contracts/interfaces/IERC20.sol + +pragma solidity >=0.5.0; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint 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 (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); +} + +// File: contracts/libraries/SafeERC20.sol + + +pragma solidity 0.6.12; + + +library SafeERC20 { + function safeSymbol(IERC20 token) internal view returns(string memory) { + (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x95d89b41)); + return success && data.length > 0 ? abi.decode(data, (string)) : "???"; + } + + function safeName(IERC20 token) internal view returns(string memory) { + (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x06fdde03)); + return success && data.length > 0 ? abi.decode(data, (string)) : "???"; + } + + function safeDecimals(IERC20 token) public view returns (uint8) { + (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x313ce567)); + return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; + } + + function safeTransfer(IERC20 token, address to, uint256 amount) internal { + (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0xa9059cbb, to, amount)); + require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: Transfer failed"); + } + + function safeTransferFrom(IERC20 token, address from, uint256 amount) internal { + (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0x23b872dd, from, address(this), amount)); + require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: TransferFrom failed"); + } +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2ERC20.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2ERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint 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 (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint 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 (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint 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 (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint 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 (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + 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, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + 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 (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Factory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + 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(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + +// File: contracts/Ownable.sol + + + +pragma solidity 0.6.12; + +// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol + +contract OwnableData { + address public owner; + address public pendingOwner; +} + +contract Ownable is OwnableData { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () internal { + owner = msg.sender; + emit OwnershipTransferred(address(0), msg.sender); + } + + function transferOwnership(address newOwner, bool direct, bool renounce) public onlyOwner { + if (direct) { + require(newOwner != address(0) || renounce, "Ownable: zero address"); + + // Effects + emit OwnershipTransferred(owner, newOwner); + owner = newOwner; + } else { + pendingOwner = newOwner; + } + } + + function claimOwnership() public { + address _pendingOwner = pendingOwner; + + require(msg.sender == _pendingOwner, "Ownable: caller != pending owner"); + + emit OwnershipTransferred(owner, _pendingOwner); + owner = _pendingOwner; + pendingOwner = address(0); + } + + modifier onlyOwner() { + require(msg.sender == owner, "Ownable: caller is not the owner"); + _; + } +} + +// File: contracts/QuickConverter.sol + + + +// P1 - P3: OK +pragma solidity 0.6.12; + + + + + + + +//QUICK LAIR'S CONVERTER CONTRACT. This contract converts all tokens into QUICK + +contract QuickConverter is Ownable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // V1 - V5: OK + IUniswapV2Factory public immutable factory; + //0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32 + // V1 - V5: OK + address public immutable dragonLair; + // V1 - V5: OK + address public immutable quick; + //0x831753dd7087cac61ab5644b308642cc1c33dc13 + // V1 - V5: OK + address public immutable weth; + //0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 + + address public treasury; + //0x476307DaC3FD170166e007FCaA14F0A129721463 + + mapping(address => bool) public approveList; + // Used to approve uniswap routers or other trusted contracts + + // V1 - V5: OK + mapping(address => address) internal _bridges; + + // E1: OK + event LogBridgeSet(address indexed token, address indexed bridge); + // E1: OK + event LogConvert( + address indexed server, + address indexed token0, + address indexed token1, + uint256 amount0, + uint256 amount1, + uint256 amountQUICK + ); + + event TreasuryChanged(address indexed treasury); + + event ApprovedSenderChanged(address indexed sender, bool isApproved); + + constructor( + address _factory, + address _dragonLair, + address _quick, + address _weth, + address _treasury + ) public { + factory = IUniswapV2Factory(_factory); + dragonLair = _dragonLair; + quick = _quick; + weth = _weth; + treasury = _treasury; + } + + // F1 - F10: OK + // C1 - C24: OK + function bridgeFor(address token) public view returns (address bridge) { + bridge = _bridges[token]; + if (bridge == address(0)) { + bridge = weth; + } + } + + // F1 - F10: OK + // C1 - C24: OK + function setBridge(address token, address bridge) external onlyOwner { + // Checks + require( + token != quick && token != weth && token != bridge, + "QuickConverter: Invalid bridge" + ); + + // Effects + _bridges[token] = bridge; + emit LogBridgeSet(token, bridge); + } + + function changeTreasury(address _treasury) external onlyOwner { + require(_treasury != address(0), "Inavlid treasury"); + treasury = _treasury; + + emit TreasuryChanged(treasury); + } + + function changeApprovedSender(address _sender, bool isApproved) external onlyOwner { + require(_sender != address(0), "Inavlid approve address"); + approveList[_sender] = isApproved; + + emit ApprovedSenderChanged(_sender, isApproved); + } + + // M1 - M5: OK + // C1 - C24: OK + // C6: It's not a fool proof solution, but it prevents flash loans, so here it's ok to use tx.origin + modifier onlyEOA() { + // Try to make flash-loan exploit harder to do by only allowing externally owned addresses. + require(approveList[msg.sender] || msg.sender == tx.origin, "QuickConverter: must be approved or use EOA"); + _; + } + + // F1 - F10: OK + // F3: _convert is separate to save gas by only checking the 'onlyEOA' modifier once in case of convertMultiple + // F6: There is an exploit to add lots of QUICK to the dragonLair, run convert, then remove the QUICK again. + // As the size of the DragonLair has grown, this requires large amounts of funds and isn't super profitable anymore + // The onlyEOA modifier prevents this being done with a flash loan. + // C1 - C24: OK + function convert(address token0, address token1) external onlyEOA() { + _convert(token0, token1); + } + + function burnPair(address token0, address token1) external onlyEOA() { + IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); + require(address(pair) != address(0), "QuickConverter: Invalid pair"); + // balanceOf: S1 - S4: OK + // transfer: X1 - X5: OK + IERC20(address(pair)).safeTransfer( + address(pair), + pair.balanceOf(address(this)) + ); + // X1 - X5: OK + (uint256 amount0, uint256 amount1) = pair.burn(address(this)); + } + + function convertTokenToQuick(address token) external onlyEOA() { + uint256 balance = IERC20(token).balanceOf(address(this)); + + if (balance > 0) { + _convertStep(token, token, balance, 0); + + uint256 amountQUICK = IERC20(quick).balanceOf(address(this)); + + uint256 dragonLairShare = amountQUICK.mul(80).div(100); + uint256 treasuryShare = amountQUICK.sub(dragonLairShare); + IERC20(quick).safeTransfer(dragonLair, dragonLairShare); + IERC20(quick).safeTransfer(treasury, treasuryShare); + emit LogConvert( + msg.sender, + token, + token, + balance, + balance, + amountQUICK + ); + } + } + + // F1 - F10: OK, see convert + // C1 - C24: OK + // C3: Loop is under control of the caller + function convertMultiple( + address[] calldata token0, + address[] calldata token1 + ) external onlyEOA() { + // TODO: This can be optimized a fair bit, but this is safer and simpler for now + uint256 len = token0.length; + for (uint256 i = 0; i < len; i++) { + _convert(token0[i], token1[i]); + } + } + + // F1 - F10: OK + // C1- C24: OK + function _convert(address token0, address token1) internal { + // Interactions + // S1 - S4: OK + IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); + require(address(pair) != address(0), "QuickConverter: Invalid pair"); + // balanceOf: S1 - S4: OK + // transfer: X1 - X5: OK + IERC20(address(pair)).safeTransfer( + address(pair), + pair.balanceOf(address(this)) + ); + // X1 - X5: OK + (uint256 amount0, uint256 amount1) = pair.burn(address(this)); + if (token0 != pair.token0()) { + (amount0, amount1) = (amount1, amount0); + } + + if (amount0 > 0 || amount1 > 0) { + _convertStep(token0, token1, amount0, amount1); + + uint256 amountQUICK = IERC20(quick).balanceOf(address(this)); + + uint256 dragonLairShare = amountQUICK.mul(80).div(100); + uint256 treasuryShare = amountQUICK.sub(dragonLairShare); + IERC20(quick).safeTransfer(dragonLair, dragonLairShare); + IERC20(quick).safeTransfer(treasury, treasuryShare); + emit LogConvert( + msg.sender, + token0, + token1, + amount0, + amount1, + amountQUICK + ); + } + + } + + // F1 - F10: OK + // C1 - C24: OK + // All _swap, _toQUICK, _convertStep: X1 - X5: OK + function _convertStep( + address token0, + address token1, + uint256 amount0, + uint256 amount1 + ) internal returns (uint256 quickOut) { + // Interactions + if (token0 == token1) { + uint256 amount = amount0.add(amount1); + if (token0 == quick) { + quickOut = amount; + } else if (token0 == weth) { + quickOut = _toQUICK(weth, amount); + } else { + address bridge = bridgeFor(token0); + amount = _swap(token0, bridge, amount, address(this)); + quickOut = _convertStep(bridge, bridge, amount, 0); + } + } else if (token0 == quick) { + // eg. QUICK - ETH + quickOut = _toQUICK(token1, amount1).add(amount0); + } else if (token1 == quick) { + // eg. USDT - QUICK + quickOut = _toQUICK(token0, amount0).add(amount1); + } else if (token0 == weth) { + // eg. ETH - USDC + quickOut = _toQUICK( + weth, + _swap(token1, weth, amount1, address(this)).add(amount0) + ); + } else if (token1 == weth) { + // eg. USDT - ETH + quickOut = _toQUICK( + weth, + _swap(token0, weth, amount0, address(this)).add(amount1) + ); + } else { + // eg. MIC - USDT + address bridge0 = bridgeFor(token0); + address bridge1 = bridgeFor(token1); + if (bridge0 == token1) { + // eg. MIC - USDT - and bridgeFor(MIC) = USDT + quickOut = _convertStep( + bridge0, + token1, + _swap(token0, bridge0, amount0, address(this)), + amount1 + ); + } else if (bridge1 == token0) { + // eg. WBTC - DSD - and bridgeFor(DSD) = WBTC + quickOut = _convertStep( + token0, + bridge1, + amount0, + _swap(token1, bridge1, amount1, address(this)) + ); + } else { + quickOut = _convertStep( + bridge0, + bridge1, // eg. USDT - DSD - and bridgeFor(DSD) = WBTC + _swap(token0, bridge0, amount0, address(this)), + _swap(token1, bridge1, amount1, address(this)) + ); + } + } + } + + // F1 - F10: OK + // C1 - C24: OK + // All safeTransfer, swap: X1 - X5: OK + function _swap( + address fromToken, + address toToken, + uint256 amountIn, + address to + ) internal returns (uint256 amountOut) { + // Checks + // X1 - X5: OK + IUniswapV2Pair pair = + IUniswapV2Pair(factory.getPair(fromToken, toToken)); + require(address(pair) != address(0), "QuickConverter: Cannot convert"); + + + IERC20(fromToken).safeTransfer(address(pair), amountIn); + + // Interactions + // X1 - X5: OK + (uint256 reserve0, uint256 reserve1, ) = pair.getReserves(); + if (fromToken == pair.token0()) { + uint256 amountInput = IERC20(fromToken).balanceOf(address(pair)).sub(reserve0); + uint256 amountInWithFee = amountInput.mul(997); + amountOut = + amountInWithFee.mul(reserve1) / + reserve0.mul(1000).add(amountInWithFee); + + pair.swap(0, amountOut, to, new bytes(0)); + // TODO: Add maximum slippage? + } else { + uint256 amountInput = IERC20(fromToken).balanceOf(address(pair)).sub(reserve1); + uint256 amountInWithFee = amountInput.mul(997); + amountOut = + amountInWithFee.mul(reserve0) / + reserve1.mul(1000).add(amountInWithFee); + pair.swap(amountOut, 0, to, new bytes(0)); + // TODO: Add maximum slippage? + } + } + + // F1 - F10: OK + // C1 - C24: OK + function _toQUICK(address token, uint256 amountIn) + internal + returns (uint256 amountOut) + { + // X1 - X5: OK + amountOut = _swap(token, quick, amountIn, address(this)); + } +} diff --git a/contracts/UniswapV2Router02a.sol b/contracts/UniswapV2Router02a.sol new file mode 100644 index 0000000..a5ba1a2 --- /dev/null +++ b/contracts/UniswapV2Router02a.sol @@ -0,0 +1,83 @@ +pragma solidity =0.6.12; + +import './UniswapV2Router02.sol'; +import './interfaces/IQuickConverter'; + +contract UniswapV2Router02a is UniswapV2Router02 { + + IQuickConverter public immutable converter; + + constructor(address _factory, address _WETH, address _converter) public UniswapV2Router02(_factory, _WETH) { + converter = IQuickConverter(_converter); + } + + // Identical to the regular addLiquidity, plus the last line of converter.convert + // We cannot override _addLiquidity instead because it doesn't call mint(), which must be called before convert() + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { + (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); + address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); + TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA); + TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB); + liquidity = IUniswapV2Pair(pair).mint(to); + converter.convert(tokenA, tokenB); + } + + // Identical to the regular addLiquidityETH, plus the last line of converter.convert + // We cannot override _addLiquidity instead because it doesn't call mint(), which must be called before convert() + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) { + (amountToken, amountETH) = _addLiquidity( + token, + WETH, + amountTokenDesired, + msg.value, + amountTokenMin, + amountETHMin + ); + address pair = UniswapV2Library.pairFor(factory, token, WETH); + TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken); + IWETH(WETH).deposit{value: amountETH}(); + assert(IWETH(WETH).transfer(pair, amountETH)); + liquidity = IUniswapV2Pair(pair).mint(to); + // refund dust eth, if any + if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH); + converter.convert(token, WETH); + } + + // Identical to the regular removeLiquidity, plus the last line of converter.convert + // This calls burn(), so it's safe to just override this one. + // Any calls after this are just returning funds to the user, so don't affect the conversion + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) { + address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); + IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair + (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to); + (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB); + (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); + require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT'); + require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT'); + converter.convert(tokenA, tokenB); + } +} diff --git a/contracts/interfaces/IQuickConverter.sol b/contracts/interfaces/IQuickConverter.sol new file mode 100644 index 0000000..b9e8aeb --- /dev/null +++ b/contracts/interfaces/IQuickConverter.sol @@ -0,0 +1,5 @@ +pragma solidity =0.6.12; + +interface IQuickConverter { + function convert(address token0, address token1) external; +} From 3362396a175c6a017d99adc69d8162eefd377d0e Mon Sep 17 00:00:00 2001 From: Paul Barclay Date: Fri, 18 Jun 2021 00:01:44 -0700 Subject: [PATCH 2/2] Switch Quick Converter from token/token inputs to Pair inputs --- contracts/QuickConverter02a.sol | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/contracts/QuickConverter02a.sol b/contracts/QuickConverter02a.sol index b6bee47..b4b9523 100755 --- a/contracts/QuickConverter02a.sol +++ b/contracts/QuickConverter02a.sol @@ -348,11 +348,11 @@ contract QuickConverter is Ownable { // As the size of the DragonLair has grown, this requires large amounts of funds and isn't super profitable anymore // The onlyEOA modifier prevents this being done with a flash loan. // C1 - C24: OK - function convert(address token0, address token1) external onlyEOA() { - _convert(token0, token1); + function convert(IUniswapV2Pair pair) external onlyEOA() { + _convert(pair); } - function burnPair(address token0, address token1) external onlyEOA() { + /*function burnPair(address token0, address token1) external onlyEOA() { IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); require(address(pair) != address(0), "QuickConverter: Invalid pair"); // balanceOf: S1 - S4: OK @@ -363,9 +363,9 @@ contract QuickConverter is Ownable { ); // X1 - X5: OK (uint256 amount0, uint256 amount1) = pair.burn(address(this)); - } + }*/ - function convertTokenToQuick(address token) external onlyEOA() { + /*function convertTokenToQuick(address token) external onlyEOA() { uint256 balance = IERC20(token).balanceOf(address(this)); if (balance > 0) { @@ -386,29 +386,30 @@ contract QuickConverter is Ownable { amountQUICK ); } - } + }*/ // F1 - F10: OK, see convert // C1 - C24: OK // C3: Loop is under control of the caller function convertMultiple( - address[] calldata token0, - address[] calldata token1 + IUniswapV2Pair[] calldata pairs, ) external onlyEOA() { // TODO: This can be optimized a fair bit, but this is safer and simpler for now - uint256 len = token0.length; + uint256 len = pairs.length; for (uint256 i = 0; i < len; i++) { - _convert(token0[i], token1[i]); + _convert(pairs[i]); } } // F1 - F10: OK // C1- C24: OK - function _convert(address token0, address token1) internal { + function _convert(IUniswapV2Pair pair) internal { // Interactions // S1 - S4: OK - IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); - require(address(pair) != address(0), "QuickConverter: Invalid pair"); + address token0 = pair.token0(); + address token1 = pair.token1(); + require(address(token0) != address(0), "QuickConverter: Invalid pair"); + require(address(token1) != address(0), "QuickConverter: Invalid pair"); // balanceOf: S1 - S4: OK // transfer: X1 - X5: OK IERC20(address(pair)).safeTransfer( @@ -417,9 +418,9 @@ contract QuickConverter is Ownable { ); // X1 - X5: OK (uint256 amount0, uint256 amount1) = pair.burn(address(this)); - if (token0 != pair.token0()) { + /*if (token0 != pair.token0()) { (amount0, amount1) = (amount1, amount0); - } + }*/ if (amount0 > 0 || amount1 > 0) { _convertStep(token0, token1, amount0, amount1);