diff --git a/contracts/IOneSplit.sol b/contracts/IOneSplit.sol index 38c9d1a..c2d3fa9 100644 --- a/contracts/IOneSplit.sol +++ b/contracts/IOneSplit.sol @@ -32,6 +32,7 @@ contract IOneSplitConsts { uint256 public constant FLAG_ENABLE_UNISWAP_CHAI = 0x200000; // Works only when ETH<>DAI or FLAG_ENABLE_MULTI_PATH_ETH uint256 public constant FLAG_ENABLE_UNISWAP_AAVE = 0x400000; // Works only when one of assets is ETH or FLAG_ENABLE_MULTI_PATH_ETH uint256 public constant FLAG_DISABLE_IDLE = 0x800000; + uint256 public constant FLAG_DISABLE_UNISWAP_POOL_TOKEN = 0x1000000; } diff --git a/contracts/OneSplit.sol b/contracts/OneSplit.sol index 77bd31d..7633cac 100644 --- a/contracts/OneSplit.sol +++ b/contracts/OneSplit.sol @@ -11,6 +11,7 @@ import "./OneSplitIearn.sol"; import "./OneSplitIdle.sol"; import "./OneSplitAave.sol"; import "./OneSplitWeth.sol"; +import "./OneSplitUniswapPoolToken.sol"; //import "./OneSplitSmartToken.sol"; @@ -25,7 +26,8 @@ contract OneSplitView is OneSplitCompoundView, OneSplitIearnView, OneSplitIdleView(0x23E4D1536c449e4D79E5903B4A9ddc3655be8609), - OneSplitWethView + OneSplitWethView, + OneSplitUniswapPoolTokenView //OneSplitSmartTokenView { function() external { @@ -98,7 +100,8 @@ contract OneSplit is OneSplitCompound, OneSplitIearn, OneSplitIdle(0x23E4D1536c449e4D79E5903B4A9ddc3655be8609), - OneSplitWeth + OneSplitWeth, + OneSplitUniswapPoolToken //OneSplitSmartToken { IOneSplitView public oneSplitView; diff --git a/contracts/OneSplitUniswapPoolToken.sol b/contracts/OneSplitUniswapPoolToken.sol new file mode 100644 index 0000000..63e7632 --- /dev/null +++ b/contracts/OneSplitUniswapPoolToken.sol @@ -0,0 +1,463 @@ +pragma solidity ^0.5.0; + +import "./interface/IUniswapExchange.sol"; +import "./interface/IUniswapFactory.sol"; +import "./OneSplitBase.sol"; + + +contract OneSplitUniswapPoolTokenBase { + using SafeMath for uint256; + + IUniswapFactory uniswapFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + return address(uniswapFactory.getToken(address(token))) != address(0); + } + + function getMaxPossibleFund( + IERC20 poolToken, + IERC20 uniswapToken, + uint256 tokenAmount, + uint256 existEthAmount + ) + internal + view + returns ( + uint256, + uint256 + ) + { + uint256 ethReserve = address(poolToken).balance; + uint256 totalLiquidity = poolToken.totalSupply(); + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + + uint256 possibleEthAmount = ethReserve.mul( + tokenAmount.sub(1) + ).div(tokenReserve); + + if (existEthAmount > possibleEthAmount) { + return ( + possibleEthAmount, + possibleEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + + return ( + existEthAmount, + existEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + +} + +contract OneSplitUniswapPoolTokenView is OneSplitBaseView, OneSplitUniswapPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + internal + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!disableFlags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + } + + function _getExpectedReturnFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 totalSupply = poolToken.totalSupply(); + + uint256 ethReserve = address(poolToken).balance; + uint256 ethAmount = amount.mul(ethReserve).div(totalSupply); + + if (!toToken.isETH()) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + ETH_ADDRESS, + toToken, + ethAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + returnAmount = returnAmount.add(ethAmount); + } + + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + uint256 exchangeTokenAmount = amount.mul(tokenReserve).div(totalSupply); + + if (toToken != uniswapToken) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + uniswapToken, + toToken, + exchangeTokenAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + returnAmount = returnAmount.add(exchangeTokenAmount); + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + uint256[] memory dist = new uint256[](DEXES_COUNT); + + uint256 ethAmount; + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + (ethAmount, dist) = super.getExpectedReturn( + fromToken, + ETH_ADDRESS, + partAmountForEth, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + ethAmount = partAmountForEth; + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 tokenAmount; + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + (tokenAmount, dist) = super.getExpectedReturn( + fromToken, + uniswapToken, + partAmountForToken, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + tokenAmount = partAmountForToken; + } + + (, returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenAmount, + ethAmount + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapPoolToken is OneSplitBase, OneSplitUniswapPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!disableFlags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + + function _swapFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + + ( + uint256 ethAmount, + uint256 exchangeTokenAmount + ) = IUniswapExchange(address(poolToken)).removeLiquidity( + amount, + 1, + 1, + now.add(1800) + ); + + if (!toToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + ETH_ADDRESS, + toToken, + ethAmount, + dist, + disableFlags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + if (toToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + uniswapToken, + toToken, + exchangeTokenAmount, + dist, + disableFlags + ); + } + } + + function _swapToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + fromToken, + ETH_ADDRESS, + partAmountForEth, + dist, + disableFlags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + fromToken, + uniswapToken, + partAmountForToken, + dist, + disableFlags + ); + + _infiniteApproveIfNeeded(uniswapToken, address(poolToken)); + } + + uint256 ethBalance = address(this).balance; + uint256 tokenBalance = uniswapToken.balanceOf(address(this)); + + (uint256 ethAmount, uint256 returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenBalance, + ethBalance + ); + + IUniswapExchange(address(poolToken)).addLiquidity.value(ethAmount)( + returnAmount.mul(995).div(1000), // 0.5% slippage + uint256(-1), // todo: think about another value + now.add(1800) + ); + + // todo: do we need to check difference between balance before and balance after? + uniswapToken.universalTransfer(msg.sender, uniswapToken.balanceOf(address(this))); + ETH_ADDRESS.universalTransfer(msg.sender, address(this).balance); + } +} diff --git a/contracts/interface/IUniswapExchange.sol b/contracts/interface/IUniswapExchange.sol index f1200c3..0196bd6 100644 --- a/contracts/interface/IUniswapExchange.sol +++ b/contracts/interface/IUniswapExchange.sol @@ -24,4 +24,8 @@ interface IUniswapExchange { uint256 deadline, address tokenAddr ) external returns (uint256 tokensBought); + + function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); + + function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); } diff --git a/contracts/interface/IUniswapFactory.sol b/contracts/interface/IUniswapFactory.sol index bc352fc..14f9069 100644 --- a/contracts/interface/IUniswapFactory.sol +++ b/contracts/interface/IUniswapFactory.sol @@ -5,4 +5,6 @@ import "./IUniswapExchange.sol"; interface IUniswapFactory { function getExchange(IERC20 token) external view returns (IUniswapExchange exchange); + + function getToken(address exchange) external view returns (IERC20 token); }