pragma solidity ^0.5.7; interface TokenInterface { function allowance(address, address) external view returns (uint); function balanceOf(address) external view returns (uint); function approve(address, uint) external; function transfer(address, uint) external returns (bool); function transferFrom(address, address, uint) external returns (bool); function deposit() external payable; function withdraw(uint) external; } interface UniswapExchange { function getEthToTokenInputPrice(uint ethSold) external view returns (uint tokenBought); function getTokenToEthInputPrice(uint tokenSold) external view returns (uint ethBought); function ethToTokenSwapInput(uint minTokens, uint deadline) external payable returns (uint tokenBought); function tokenToEthSwapInput(uint tokenSold, uint minEth, uint deadline) external returns (uint ethBought); } interface KyberInterface { function trade( address src, uint srcAmount, address dest, address destAddress, uint maxDestAmount, uint minConversionRate, address walletId ) external payable returns (uint); function getExpectedRate( address src, address dest, uint srcQty ) external view returns (uint, uint); } interface Eth2DaiInterface { function getBuyAmount(address dest, address src, uint srcAmt) external view returns(uint); function getPayAmount(address src, address dest, uint destAmt) external view returns (uint); function sellAllAmount( address src, uint srcAmt, address dest, uint minDest ) external returns (uint destAmt); function buyAllAmount( address dest, uint destAmt, address src, uint maxSrc ) external returns (uint srcAmt); } contract DSMath { function add(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, "math-not-safe"); } 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, "math-not-safe"); } uint constant WAD = 10 ** 18; uint constant RAY = 10 ** 27; function rmul(uint x, uint y) internal pure returns (uint z) { z = add(mul(x, y), RAY / 2) / RAY; } function rdiv(uint x, uint y) internal pure returns (uint z) { z = add(mul(x, RAY), y / 2) / y; } function wmul(uint x, uint y) internal pure returns (uint z) { z = add(mul(x, y), WAD / 2) / WAD; } function wdiv(uint x, uint y) internal pure returns (uint z) { z = add(mul(x, WAD), y / 2) / y; } } contract Helper is DSMath { address public eth2daiAddr = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; address public uniswapAddr = 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14; // Uniswap DAI exchange address public kyberAddr = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; address public ethAddr = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public wethAddr = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public daiAddr = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; address public adminOne = 0xa7615CD307F323172331865181DC8b80a2834324; address public adminTwo = 0x7284a8451d9a0e7Dc62B3a71C0593eA2eC5c5638; uint public maxSplitAmtEth = 60000000000000000000; uint public maxSplitAmtDai = 20000000000000000000000; uint public cut = 1000000000000000000; // 0% charge uint public minDai = 200000000000000000000; // DAI < 200 swap with Kyber or Uniswap uint public minEth = 1000000000000000000; // ETH < 1 swap with Kyber or Uniswap function setAllowance(TokenInterface _token, address _spender) internal { if (_token.allowance(address(this), _spender) != uint(-1)) { _token.approve(_spender, uint(-1)); } } modifier isAdmin { require(msg.sender == adminOne || msg.sender == adminTwo, "Not an Admin"); _; } } contract AdminStuffs is Helper { function setSplitEth(uint amt) public isAdmin { maxSplitAmtEth = amt; } function setSplitDai(uint amt) public isAdmin { maxSplitAmtDai = amt; } function withdrawToken(address token) public isAdmin { uint daiBal = TokenInterface(token).balanceOf(address(this)); TokenInterface(token).transfer(msg.sender, daiBal); } function withdrawEth() public payable isAdmin { msg.sender.transfer(address(this).balance); } function changeFee(uint amt) public isAdmin { if (amt < 997000000000000000) { cut = 997000000000000000; // maximum fees can be 0.3%. Minimum 0% } else { cut = amt; } } function changeMinEth(uint amt) public isAdmin { minEth = amt; } function changeMinDai(uint amt) public isAdmin { minDai = amt; } } contract SplitHelper is AdminStuffs { function getBest(address src, address dest, uint srcAmt) public view returns (uint bestExchange, uint destAmt) { uint finalSrcAmt = srcAmt; if (src == daiAddr) { finalSrcAmt = wmul(srcAmt, cut); } uint eth2DaiPrice = getRateEth2Dai(src, dest, finalSrcAmt); uint kyberPrice = getRateKyber(src, dest, finalSrcAmt); uint uniswapPrice = getRateUniswap(src, dest, finalSrcAmt); if (eth2DaiPrice > kyberPrice && eth2DaiPrice > uniswapPrice) { destAmt = eth2DaiPrice; bestExchange = 0; } else if (kyberPrice > eth2DaiPrice && kyberPrice > uniswapPrice) { destAmt = kyberPrice; bestExchange = 1; } else { destAmt = uniswapPrice; bestExchange = 2; } if (dest == daiAddr) { destAmt = wmul(destAmt, cut); } require(destAmt != 0, "Dest Amt = 0"); } function getBestUniswapKyber(address src, address dest, uint srcAmt) public view returns (uint bestExchange, uint destAmt) { uint finalSrcAmt = srcAmt; if (src == daiAddr) { finalSrcAmt = wmul(srcAmt, cut); } uint kyberPrice = getRateKyber(src, dest, finalSrcAmt); uint uniswapPrice = getRateUniswap(src, dest, finalSrcAmt); if (kyberPrice >= uniswapPrice) { destAmt = kyberPrice; bestExchange = 1; } else { destAmt = uniswapPrice; bestExchange = 2; } if (dest == daiAddr) { destAmt = wmul(destAmt, cut); } require(destAmt != 0, "Dest Amt = 0"); } function getRateEth2Dai(address src, address dest, uint srcAmt) internal view returns (uint destAmt) { if (src == ethAddr) { destAmt = Eth2DaiInterface(eth2daiAddr).getBuyAmount(dest, wethAddr, srcAmt); } else if (dest == ethAddr) { destAmt = Eth2DaiInterface(eth2daiAddr).getBuyAmount(wethAddr, src, srcAmt); } } function getRateKyber(address src, address dest, uint srcAmt) internal view returns (uint destAmt) { (uint kyberPrice,) = KyberInterface(kyberAddr).getExpectedRate(src, dest, srcAmt); destAmt = wmul(srcAmt, kyberPrice); } function getRateUniswap(address src, address dest, uint srcAmt) internal view returns (uint destAmt) { if (src == ethAddr) { destAmt = UniswapExchange(uniswapAddr).getEthToTokenInputPrice(srcAmt); } else if (dest == ethAddr) { destAmt = UniswapExchange(uniswapAddr).getTokenToEthInputPrice(srcAmt); } } } contract SplitResolver is SplitHelper { event LogEthToDai(address user, uint srcAmt, uint destAmt); event LogDaiToEth(address user, uint srcAmt, uint destAmt); function swapEth2Dai(address src, address dest, uint srcAmt) internal returns (uint destAmt) { if (src == wethAddr) { TokenInterface(wethAddr).deposit.value(srcAmt)(); } destAmt = Eth2DaiInterface(eth2daiAddr).sellAllAmount( src, srcAmt, dest, 0 ); } function swapKyber(address src, address dest, uint srcAmt) internal returns (uint destAmt) { uint ethAmt = src == ethAddr ? srcAmt : 0; destAmt = KyberInterface(kyberAddr).trade.value(ethAmt)( src, srcAmt, dest, address(this), 2**255, 0, adminOne ); } function swapUniswap(address src, address dest, uint srcAmt) internal returns (uint destAmt) { if (src == ethAddr) { destAmt = UniswapExchange(uniswapAddr).ethToTokenSwapInput.value(srcAmt)(1, block.timestamp + 1); } else if (dest == ethAddr) { destAmt = UniswapExchange(uniswapAddr).tokenToEthSwapInput(srcAmt, 1, block.timestamp + 1); } } function ethToDaiBestSwap(uint bestExchange, uint amtToSwap) internal returns (uint destAmt) { if (bestExchange == 0) { destAmt += swapEth2Dai(wethAddr, daiAddr, amtToSwap); } else if (bestExchange == 1) { destAmt += swapKyber(ethAddr, daiAddr, amtToSwap); } else { destAmt += swapUniswap(ethAddr, daiAddr, amtToSwap); } } function ethToDaiLoop(uint srcAmt, uint splitAmt, uint finalAmt) internal returns (uint destAmt) { if (srcAmt > splitAmt) { uint amtToSwap = splitAmt; uint nextSrcAmt = srcAmt - splitAmt; (uint bestExchange,) = getBest(ethAddr, daiAddr, amtToSwap); uint daiBought = finalAmt; daiBought += ethToDaiBestSwap(bestExchange, amtToSwap); destAmt = ethToDaiLoop(nextSrcAmt, splitAmt, daiBought); } else if (srcAmt > minEth) { (uint bestExchange,) = getBest(ethAddr, daiAddr, srcAmt); destAmt = finalAmt; destAmt += ethToDaiBestSwap(bestExchange, srcAmt); } else if (srcAmt > 0) { (uint bestExchange,) = getBestUniswapKyber(ethAddr, daiAddr, srcAmt); destAmt = finalAmt; destAmt += ethToDaiBestSwap(bestExchange, srcAmt); } else { destAmt = finalAmt; } } function daiToEthBestSwap(uint bestExchange, uint amtToSwap) internal returns (uint destAmt) { if (bestExchange == 0) { destAmt += swapEth2Dai(daiAddr, wethAddr, amtToSwap); } else if (bestExchange == 1) { destAmt += swapKyber(daiAddr, ethAddr, amtToSwap); } else { destAmt += swapUniswap(daiAddr, ethAddr, amtToSwap); } } function daiToEthLoop(uint srcAmt, uint splitAmt, uint finalAmt) internal returns (uint destAmt) { if (srcAmt > splitAmt) { uint amtToSwap = splitAmt; uint nextSrcAmt = srcAmt - splitAmt; (uint bestExchange,) = getBest(daiAddr, ethAddr, amtToSwap); uint ethBought = finalAmt; ethBought += daiToEthBestSwap(bestExchange, amtToSwap); destAmt = daiToEthLoop(nextSrcAmt, splitAmt, ethBought); } else if (srcAmt > minDai) { (uint bestExchange,) = getBest(daiAddr, ethAddr, srcAmt); destAmt = finalAmt; destAmt += daiToEthBestSwap(bestExchange, srcAmt); } else if (srcAmt > 0) { (uint bestExchange,) = getBestUniswapKyber(daiAddr, ethAddr, srcAmt); destAmt = finalAmt; destAmt += daiToEthBestSwap(bestExchange, srcAmt); } else { destAmt = finalAmt; } } function wethToEth() internal { TokenInterface wethContract = TokenInterface(wethAddr); uint balanceWeth = wethContract.balanceOf(address(this)); if (balanceWeth > 0) { wethContract.withdraw(balanceWeth); } } } contract Swap is SplitResolver { function ethToDaiSwap(uint splitAmt, uint slippageAmt) public payable returns (uint destAmt) { // srcAmt = msg.value require(maxSplitAmtEth >= splitAmt, "split amt > max"); destAmt = ethToDaiLoop(msg.value, splitAmt, 0); destAmt = wmul(destAmt, cut); require(destAmt > slippageAmt, "Dest Amt < slippage"); require(TokenInterface(daiAddr).transfer(msg.sender, destAmt), "Not enough DAI to transfer"); emit LogEthToDai(msg.sender, msg.value, destAmt); } function daiToEthSwap(uint srcAmt, uint splitAmt, uint slippageAmt) public returns (uint destAmt) { require(maxSplitAmtDai >= splitAmt, "split amt > max"); require(TokenInterface(daiAddr).transferFrom(msg.sender, address(this), srcAmt), "Token Approved?"); uint finalSrcAmt = wmul(srcAmt, cut); destAmt = daiToEthLoop(finalSrcAmt, splitAmt, 0); wethToEth(); require(destAmt > slippageAmt, "Dest Amt < slippage"); msg.sender.transfer(destAmt); emit LogDaiToEth(msg.sender, finalSrcAmt, destAmt); } } contract SplitSwap is Swap { constructor() public { setAllowance(TokenInterface(daiAddr), eth2daiAddr); setAllowance(TokenInterface(daiAddr), kyberAddr); setAllowance(TokenInterface(daiAddr), uniswapAddr); setAllowance(TokenInterface(wethAddr), eth2daiAddr); setAllowance(TokenInterface(wethAddr), wethAddr); } function() external payable {} }