pragma solidity ^0.5.0; interface TubInterface { function open() external returns (bytes32); function join(uint) external; function exit(uint) external; function lock(bytes32, uint) external; function free(bytes32, uint) external; function draw(bytes32, uint) external; function wipe(bytes32, uint) external; function give(bytes32, address) external; function shut(bytes32) external; function cups(bytes32) external view returns (address, uint, uint, uint); function gem() external view returns (TokenInterface); function gov() external view returns (TokenInterface); function skr() external view returns (TokenInterface); function sai() external view returns (TokenInterface); function ink(bytes32) external view returns (uint); function tab(bytes32) external returns (uint); function rap(bytes32) external returns (uint); function per() external view returns (uint); function pep() external view returns (PepInterface); } interface PepInterface { function peek() external returns (bytes32, bool); } interface oracleInterface { function read() external view returns (bytes32); } interface UniswapExchange { function getEthToTokenOutputPrice(uint256 tokensBought) external view returns (uint256 ethSold); function getTokenToEthOutputPrice(uint256 ethBought) external view returns (uint256 tokensSold); function tokenToTokenSwapOutput( uint256 tokensBought, uint256 maxTokensSold, uint256 maxEthSold, uint256 deadline, address tokenAddr ) external returns (uint256 tokensSold); } 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 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 SplitSwapInterface { function getBest(address src, address dest, uint srcAmt) external view returns (uint bestExchange, uint destAmt); function ethToDaiSwap(uint splitAmt, uint slippageAmt) external payable returns (uint destAmt); function daiToEthSwap(uint srcAmt, uint splitAmt, uint slippageAmt) external returns (uint destAmt); } 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 Helpers is DSMath { /** * @dev get MakerDAO CDP engine */ function getSaiTubAddress() public pure returns (address sai) { sai = 0x448a5065aeBB8E423F0896E6c5D525C040f59af3; } /** * @dev get MakerDAO Oracle for ETH price */ function getOracleAddress() public pure returns (address oracle) { oracle = 0x729D19f657BD0614b4985Cf1D82531c67569197B; } /** * @dev get uniswap MKR exchange */ function getUniswapMKRExchange() public pure returns (address ume) { ume = 0x2C4Bd064b998838076fa341A83d007FC2FA50957; } /** * @dev get uniswap DAI exchange */ function getUniswapDAIExchange() public pure returns (address ude) { ude = 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14; } /** * @dev get ethereum address for trade */ function getAddressETH() public pure returns (address eth) { eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } /** * @dev get dai address for trade */ function getAddressDAI() public pure returns (address dai) { dai = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; } /** * @dev get kyber proxy address */ function getAddressKyber() public pure returns (address kyber) { kyber = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; } /** * @dev get admin address */ function getAddressSplitSwap() public pure returns (address payable splitSwap) { splitSwap = 0x5D05EA343C7a13cee09b14e56FCBe985c25521b7; } /** * @dev get admin address */ function getAddressAdmin() public pure returns (address payable admin) { admin = 0x7284a8451d9a0e7Dc62B3a71C0593eA2eC5c5638; } function getCDPStats(bytes32 cup) internal view returns (uint ethCol, uint daiDebt, uint usdPerEth) { TubInterface tub = TubInterface(getSaiTubAddress()); usdPerEth = uint(oracleInterface(getOracleAddress()).read()); (, uint pethCol, uint debt,) = tub.cups(cup); ethCol = rmul(pethCol, tub.per()); // get ETH col from PETH col daiDebt = debt; } } contract MakerHelpers is Helpers { event LogLock(uint cdpNum, uint amtETH, uint amtPETH, address owner); event LogFree(uint cdpNum, uint amtETH, uint amtPETH, address owner); event LogDraw(uint cdpNum, uint amtDAI, address owner); event LogWipe(uint cdpNum, uint daiAmt, uint mkrFee, uint daiFee, address owner); function setAllowance(TokenInterface _token, address _spender) internal { if (_token.allowance(address(this), _spender) != uint(-1)) { _token.approve(_spender, uint(-1)); } } function lock(uint cdpNum, uint ethAmt) internal { if (ethAmt > 0) { bytes32 cup = bytes32(cdpNum); address tubAddr = getSaiTubAddress(); TubInterface tub = TubInterface(tubAddr); TokenInterface weth = tub.gem(); TokenInterface peth = tub.skr(); (address lad,,,) = tub.cups(cup); require(lad == address(this), "cup-not-owned"); weth.deposit.value(ethAmt)(); uint ink = rdiv(ethAmt, tub.per()); ink = rmul(ink, tub.per()) <= ethAmt ? ink : ink - 1; setAllowance(weth, tubAddr); tub.join(ink); setAllowance(peth, tubAddr); tub.lock(cup, ink); emit LogLock( cdpNum, ethAmt, ink, address(this) ); } } function free(uint cdpNum, uint jam) internal { if (jam > 0) { bytes32 cup = bytes32(cdpNum); address tubAddr = getSaiTubAddress(); TubInterface tub = TubInterface(tubAddr); TokenInterface peth = tub.skr(); TokenInterface weth = tub.gem(); uint ink = rdiv(jam, tub.per()); ink = rmul(ink, tub.per()) <= jam ? ink : ink - 1; tub.free(cup, ink); setAllowance(peth, tubAddr); tub.exit(ink); uint freeJam = weth.balanceOf(address(this)); // withdraw possible previous stuck WETH as well weth.withdraw(freeJam); emit LogFree( cdpNum, freeJam, ink, address(this) ); } } function draw(uint cdpNum, uint _wad) internal { bytes32 cup = bytes32(cdpNum); if (_wad > 0) { TubInterface tub = TubInterface(getSaiTubAddress()); tub.draw(cup, _wad); emit LogDraw(cdpNum, _wad, address(this)); } } function wipe(uint cdpNum, uint _wad) internal { if (_wad > 0) { TubInterface tub = TubInterface(getSaiTubAddress()); UniswapExchange daiEx = UniswapExchange(getUniswapDAIExchange()); UniswapExchange mkrEx = UniswapExchange(getUniswapMKRExchange()); TokenInterface dai = tub.sai(); TokenInterface mkr = tub.gov(); bytes32 cup = bytes32(cdpNum); (address lad,,,) = tub.cups(cup); require(lad == address(this), "cup-not-owned"); setAllowance(dai, getSaiTubAddress()); setAllowance(mkr, getSaiTubAddress()); setAllowance(dai, getUniswapDAIExchange()); (bytes32 val, bool ok) = tub.pep().peek(); // tub.rap(cup) = stability fee in $, tub.tab(cup) = total DAI debt uint mkrFee = wdiv(rmul(_wad, rdiv(tub.rap(cup), tub.tab(cup))), uint(val)); // uint mkrFee = wdiv(rmul(_wad, rdiv(tub.rap(cup), add(tub.rap(cup), tub.tab(cup)))), uint(val)); uint daiFeeAmt = daiEx.getTokenToEthOutputPrice(mkrEx.getEthToTokenOutputPrice(mkrFee)); uint daiAmt = sub(_wad, daiFeeAmt); if (ok && val != 0) { daiEx.tokenToTokenSwapOutput( mkrFee, daiFeeAmt, uint(999000000000000000000), uint(1899063809), // 6th March 2030 GMT // no logic address(mkr) ); } tub.wipe(cup, daiAmt); emit LogWipe( cdpNum, _wad, mkrFee, daiFeeAmt, address(this) ); } } } contract GetDetails is MakerHelpers { function getMax(uint cdpID) public view returns (uint maxColToFree, uint maxDaiToDraw, uint ethInUSD) { bytes32 cup = bytes32(cdpID); (uint ethCol, uint daiDebt, uint usdPerEth) = getCDPStats(cup); uint colToUSD = sub(wmul(ethCol, usdPerEth), 10); uint minColNeeded = add(wmul(daiDebt, 1500000000000000000), 10); maxColToFree = wdiv(sub(colToUSD, minColNeeded), usdPerEth); uint maxDebtLimit = sub(wdiv(colToUSD, 1500000000000000000), 10); maxDaiToDraw = sub(maxDebtLimit, daiDebt); ethInUSD = usdPerEth; } function getSave(uint cdpID, uint ethToSwap) public view returns (uint finalEthCol, uint finalDaiDebt, uint finalColToUSD, bool canSave) { bytes32 cup = bytes32(cdpID); (uint ethCol, uint daiDebt, uint usdPerEth) = getCDPStats(cup); (finalEthCol, finalDaiDebt, finalColToUSD, canSave) = checkSave( ethCol, daiDebt, usdPerEth, ethToSwap ); } function getLeverage( uint cdpID, uint daiToSwap ) public view returns ( uint finalEthCol, uint finalDaiDebt, uint finalColToUSD, bool canLeverage ) { bytes32 cup = bytes32(cdpID); (uint ethCol, uint daiDebt, uint usdPerEth) = getCDPStats(cup); (finalEthCol, finalDaiDebt, finalColToUSD, canLeverage) = checkLeverage( ethCol, daiDebt, usdPerEth, daiToSwap ); } function checkSave( uint ethCol, uint daiDebt, uint usdPerEth, uint ethToSwap ) internal view returns ( uint finalEthCol, uint finalDaiDebt, uint finalColToUSD, bool canSave ) { uint colToUSD = sub(wmul(ethCol, usdPerEth), 10); uint minColNeeded = add(wmul(daiDebt, 1500000000000000000), 10); uint colToFree = wdiv(sub(colToUSD, minColNeeded), usdPerEth); if (ethToSwap < colToFree) { colToFree = ethToSwap; } (, uint expectedDAI) = SplitSwapInterface(getAddressSplitSwap()).getBest(getAddressETH(), getAddressDAI(), colToFree); if (expectedDAI < daiDebt) { finalEthCol = sub(ethCol, colToFree); finalDaiDebt = sub(daiDebt, expectedDAI); finalColToUSD = wmul(finalEthCol, usdPerEth); canSave = true; } else { finalEthCol = 0; finalDaiDebt = 0; finalColToUSD = 0; canSave = false; } } function checkLeverage( uint ethCol, uint daiDebt, uint usdPerEth, uint daiToSwap ) internal view returns ( uint finalEthCol, uint finalDaiDebt, uint finalColToUSD, bool canLeverage ) { uint colToUSD = sub(wmul(ethCol, usdPerEth), 10); uint maxDebtLimit = sub(wdiv(colToUSD, 1500000000000000000), 10); uint debtToBorrow = sub(maxDebtLimit, daiDebt); if (daiToSwap < debtToBorrow) { debtToBorrow = daiToSwap; } (, uint expectedETH) = SplitSwapInterface(getAddressSplitSwap()).getBest(getAddressDAI(), getAddressETH(), debtToBorrow); if (ethCol != 0) { finalEthCol = add(ethCol, expectedETH); finalDaiDebt = add(daiDebt, debtToBorrow); finalColToUSD = wmul(finalEthCol, usdPerEth); canLeverage = true; } else { finalEthCol = 0; finalDaiDebt = 0; finalColToUSD = 0; canLeverage = false; } } } contract Save is GetDetails { /** * @param what 2 for SAVE & 3 for LEVERAGE */ event LogTrade( uint what, // 0 for BUY & 1 for SELL address src, uint srcAmt, address dest, uint destAmt, address beneficiary, uint minConversionRate, address affiliate ); event LogSaveCDP( uint cdpID, uint srcETH, uint destDAI ); event LogLeverageCDP( uint cdpID, uint srcDAI, uint destETH ); function save( uint cdpID, uint colToSwap, uint splitAmt, uint slippageAmt ) public { bytes32 cup = bytes32(cdpID); (uint ethCol, uint daiDebt, uint usdPerEth) = getCDPStats(cup); uint colToFree = getColToFree(ethCol, daiDebt, usdPerEth); require(colToFree != 0, "no-collatral-to-free"); if (colToSwap < colToFree) { colToFree = colToSwap; } free(cdpID, colToFree); uint ethToSwap = address(this).balance; ethToSwap = ethToSwap < colToFree ? ethToSwap : colToFree; uint destAmt = SplitSwapInterface(getAddressSplitSwap()).ethToDaiSwap.value(ethToSwap)(splitAmt, slippageAmt); wipe(cdpID, destAmt); emit LogSaveCDP(cdpID, ethToSwap, destAmt); } function leverage( uint cdpID, uint daiToSwap, uint splitAmt, uint slippageAmt ) public { bytes32 cup = bytes32(cdpID); (uint ethCol, uint daiDebt, uint usdPerEth) = getCDPStats(cup); uint debtToBorrow = getDebtToBorrow(ethCol, daiDebt, usdPerEth); require(debtToBorrow != 0, "No-debt-to-borrow"); if (daiToSwap < debtToBorrow) { debtToBorrow = daiToSwap; } draw(cdpID, debtToBorrow); TokenInterface(getAddressDAI()).approve(getAddressSplitSwap(), debtToBorrow); uint destAmt = SplitSwapInterface(getAddressSplitSwap()).daiToEthSwap(debtToBorrow, splitAmt, slippageAmt); lock(cdpID, destAmt); emit LogLeverageCDP(cdpID, debtToBorrow, destAmt); } function getColToFree(uint ethCol, uint daiDebt, uint usdPerEth) internal pure returns (uint colToFree) { uint colToUSD = sub(wmul(ethCol, usdPerEth), 10); uint minColNeeded = add(wmul(daiDebt, 1500000000000000000), 10); colToFree = sub(wdiv(sub(colToUSD, minColNeeded), usdPerEth), 10); } function getDebtToBorrow(uint ethCol, uint daiDebt, uint usdPerEth) internal pure returns (uint debtToBorrow) { uint colToUSD = sub(wmul(ethCol, usdPerEth), 10); uint maxDebtLimit = sub(wdiv(colToUSD, 1500000000000000000), 10); debtToBorrow = sub(maxDebtLimit, daiDebt); } } contract InstaSave is Save { function() external payable {} }