diff --git a/contracts/ProxyLogics/InstaCompSave.sol b/contracts/ProxyLogics/InstaCompSave.sol index b5b644e..02b1280 100644 --- a/contracts/ProxyLogics/InstaCompSave.sol +++ b/contracts/ProxyLogics/InstaCompSave.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.7; +pragma solidity ^0.5.8; interface CTokenInterface { function redeemUnderlying(uint redeemAmount) external returns (uint); diff --git a/contracts/ProxyLogics/MCD/InstaCompDaiSave.sol b/contracts/ProxyLogics/MCD/InstaCompDaiSave.sol new file mode 100644 index 0000000..056c8d8 --- /dev/null +++ b/contracts/ProxyLogics/MCD/InstaCompDaiSave.sol @@ -0,0 +1,380 @@ +pragma solidity ^0.5.8; + +interface CTokenInterface { + function redeemUnderlying(uint redeemAmount) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function exchangeRateCurrent() external returns (uint); + function borrowBalanceCurrent(address account) external returns (uint); + function totalSupply() external view returns (uint256); + function balanceOf(address owner) external view returns (uint256 balance); + function allowance(address, 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); +} + +interface CERC20Interface { + function mint(uint mintAmount) external returns (uint); // For ERC20 + function repayBorrow(uint repayAmount) external returns (uint); // For ERC20 + function borrowBalanceCurrent(address account) external returns (uint); +} + +interface CETHInterface { + function mint() external payable; // For ETH + function repayBorrow() external payable; // For ETH + function borrowBalanceCurrent(address account) external returns (uint); +} + +interface ERC20Interface { + 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); +} + +interface ComptrollerInterface { + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); + function getAssetsIn(address account) external view returns (address[] memory); + function getAccountLiquidity(address account) external view returns (uint, uint, uint); +} + +interface CompOracleInterface { + function getUnderlyingPrice(address) external view returns (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) { + z = x - y <= x ? x - y : 0; + } + + 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; + + 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 ethereum address for trade + */ + function getAddressETH() public pure returns (address eth) { + eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + } + + /** + * @dev get Dai v2 address + */ + function getAddressDAI() public pure returns (address dai) { + dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + } + + /** + * @dev get Compound Comptroller Address + */ + function getComptrollerAddress() public pure returns (address troller) { + troller = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; + } + + /** + * @dev get Compound Orcale Address + */ + function getCompOracleAddress() public pure returns (address troller) { + troller = 0x1D8aEdc9E924730DD3f9641CDb4D1B92B848b4bd; + } + + /** + * @dev get Compound Ceth Address + */ + function getCETHAddress() public pure returns (address cEth) { + cEth = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; + } + + /** + * @dev get Compound Dai Address + */ + function getCDAIAddress() public pure returns (address cDai) { + cDai = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643; + } + + /** + * @dev get SplitSwap address + */ + function getAddressSplitSwap() public pure returns (address payable splitSwap) { + splitSwap = 0xc51a5024280d6AB2596e4aFFe1BDf6bDc2318da2; + } + + function enterMarket(address cErc20) internal { + ComptrollerInterface troller = ComptrollerInterface(getComptrollerAddress()); + address[] memory markets = troller.getAssetsIn(address(this)); + bool isEntered = false; + for (uint i = 0; i < markets.length; i++) { + if (markets[i] == cErc20) { + isEntered = true; + } + } + if (!isEntered) { + address[] memory toEnter = new address[](1); + toEnter[0] = cErc20; + troller.enterMarkets(toEnter); + } + } + + /** + * @dev setting allowance to compound for the "user proxy" if required. + */ + function setApproval(address erc20, uint srcAmt, address to) internal { + ERC20Interface erc20Contract = ERC20Interface(erc20); + uint tokenAllowance = erc20Contract.allowance(address(this), to); + if (srcAmt > tokenAllowance) { + erc20Contract.approve(to, 2**255); + } + } +} + + +contract CompoundHelper is Helpers { + + /** + * @dev get users overall details for Compound + */ + function getCompStats( + address user, + address[] memory cTokenAddr, + uint[] memory cTokenFactor + ) public returns (uint totalSupply, uint totalBorrow, uint maxBorrow, uint borrowRemain, uint maxWithdraw, uint ratio) + { + for (uint i = 0; i < cTokenAddr.length; i++) { + address cTokenAdd = cTokenAddr[i]; + uint factor = cTokenFactor[i]; + (uint supplyInEth, uint borrowInEth) = compSupplyBorrow(cTokenAdd, user); + totalSupply += supplyInEth; + totalBorrow += borrowInEth; + maxBorrow += wmul(supplyInEth, factor); + } + borrowRemain = sub(maxBorrow, totalBorrow); + maxWithdraw = sub(wdiv(borrowRemain, 750000000000000000), 10); // divide it by 0.75 (ETH Factor) + uint userEthSupply = getEthSupply(user); + maxWithdraw = userEthSupply > maxWithdraw ? maxWithdraw : userEthSupply; + ratio = wdiv(totalBorrow, totalSupply); + } + + /** + * @dev get user's token supply and borrow in ETH + */ + function compSupplyBorrow(address cTokenAdd, address user) internal returns(uint supplyInEth, uint borrowInEth) { + CTokenInterface cTokenContract = CTokenInterface(cTokenAdd); + uint tokenPriceInEth = CompOracleInterface(getCompOracleAddress()).getUnderlyingPrice(cTokenAdd); + uint cTokenBal = sub(cTokenContract.balanceOf(user), 1); + uint cTokenExchangeRate = cTokenContract.exchangeRateCurrent(); + uint tokenSupply = sub(wmul(cTokenBal, cTokenExchangeRate), 1); + supplyInEth = sub(wmul(tokenSupply, tokenPriceInEth), 10); + uint tokenBorrowed = cTokenContract.borrowBalanceCurrent(user); + borrowInEth = add(wmul(tokenBorrowed, tokenPriceInEth), 10); + } + + function getEthSupply(address user) internal returns (uint ethSupply) { + CTokenInterface cTokenContract = CTokenInterface(getCETHAddress()); + uint cTokenBal = sub(cTokenContract.balanceOf(user), 1); + uint cTokenExchangeRate = cTokenContract.exchangeRateCurrent(); + ethSupply = wmul(cTokenBal, cTokenExchangeRate); + } + + function daiBorrowed(address user) internal returns (uint daiAmt) { + CTokenInterface cTokenContract = CTokenInterface(getCDAIAddress()); + daiAmt = cTokenContract.borrowBalanceCurrent(user); + } + + function getDaiRemainBorrow(uint daiInEth) internal view returns (uint daiAmt) { + uint tokenPriceInEth = CompOracleInterface(getCompOracleAddress()).getUnderlyingPrice(getCDAIAddress()); + daiAmt = sub(wdiv(daiInEth, tokenPriceInEth), 10); + } + +} + + +contract CompoundResolver is CompoundHelper { + + event LogMint(address erc20, address cErc20, uint tokenAmt, address owner); + event LogRedeem(address erc20, address cErc20, uint tokenAmt, address owner); + event LogBorrow(address erc20, address cErc20, uint tokenAmt, address owner); + event LogRepay(address erc20, address cErc20, uint tokenAmt, address owner); + + function getSave( + address user, + uint ethToFree, + address[] memory cTokenAddr, + uint[] memory ctokenFactor + ) public returns (uint finalColInEth, uint finalDebtInEth, uint daiDebt, bool isOk) + { + (uint totalSupply, uint totalBorrow,,,uint maxWithdraw,) = getCompStats(user, cTokenAddr, ctokenFactor); + uint ethToSwap = ethToFree < maxWithdraw ? ethToFree : maxWithdraw; + (, uint expectedDAI) = SplitSwapInterface(getAddressSplitSwap()).getBest(getAddressETH(), getAddressDAI(), ethToSwap); + uint daiBorrowed = daiBorrowed(user); + uint daiInEth = CompOracleInterface(getCompOracleAddress()).getUnderlyingPrice(getCDAIAddress()); + if (daiBorrowed < expectedDAI) { + finalColInEth = sub(totalSupply, ethToSwap); + finalDebtInEth = sub(totalBorrow, wmul(daiBorrowed, daiInEth)); + daiDebt = 0; + isOk = false; + } else { + finalColInEth = sub(totalSupply, ethToSwap); + finalDebtInEth = sub(totalBorrow, wmul(expectedDAI, daiInEth)); + daiDebt = sub(daiBorrowed, expectedDAI); + isOk = true; + } + } + + function getLeverage( + address user, + uint daiToBorrow, + address[] memory cTokenAddr, + uint[] memory ctokenFactor + ) public returns (uint finalColInEth, uint finalDebtInEth, uint ethCol) + { + (uint totalSupply, uint totalBorrow,, uint borrowRemain,,) = getCompStats(user, cTokenAddr, ctokenFactor); + uint daiToSwap = getDaiRemainBorrow(borrowRemain); + daiToSwap = daiToSwap < daiToBorrow ? daiToSwap : daiToBorrow; + (, uint expectedETH) = SplitSwapInterface(getAddressSplitSwap()).getBest(getAddressDAI(), getAddressETH(), daiToSwap); + uint daiInEth = CompOracleInterface(getCompOracleAddress()).getUnderlyingPrice(getCDAIAddress()); + finalColInEth = add(totalSupply, expectedETH); + finalDebtInEth = add(totalBorrow, wmul(daiToSwap, daiInEth)); + ethCol = add(getEthSupply(user), expectedETH); + } + + /** + * @dev Deposit ETH/ERC20 and mint Compound Tokens + */ + function mintCEth(uint tokenAmt) internal { + CETHInterface cToken = CETHInterface(getCETHAddress()); + cToken.mint.value(tokenAmt)(); + emit LogMint( + getAddressETH(), + getCETHAddress(), + tokenAmt, + msg.sender + ); + } + + /** + * @dev Redeem ETH/ERC20 and mint Compound Tokens + * @param tokenAmt Amount of token To Redeem + */ + function redeemEth(uint tokenAmt) internal { + CTokenInterface cToken = CTokenInterface(getCETHAddress()); + setApproval(getCETHAddress(), 10**30, getCETHAddress()); + require(cToken.redeemUnderlying(tokenAmt) == 0, "something went wrong"); + emit LogRedeem( + getAddressETH(), + getCETHAddress(), + tokenAmt, + address(this) + ); + } + + /** + * @dev borrow ETH/ERC20 + */ + function borrow(uint tokenAmt) internal { + require(CTokenInterface(getCDAIAddress()).borrow(tokenAmt) == 0, "got collateral?"); + emit LogBorrow( + getAddressDAI(), + getCDAIAddress(), + tokenAmt, + address(this) + ); + } + + /** + * @dev Pay Debt ETH/ERC20 + */ + function repayDai(uint tokenAmt) internal { + CERC20Interface cToken = CERC20Interface(getCDAIAddress()); + setApproval(getAddressDAI(), tokenAmt, getCDAIAddress()); + require(cToken.repayBorrow(tokenAmt) == 0, "transfer approved?"); + emit LogRepay( + getAddressDAI(), + getCDAIAddress(), + tokenAmt, + address(this) + ); + } + +} + + +contract CompoundSave is CompoundResolver { + + event LogSaveDaiCompound(uint srcETH, uint destDAI); + event LogLeverageDaiCompound(uint srcDAI,uint destETH); + + function save( + uint ethToFree, + address[] memory ctokenAddr, + uint[] memory ctokenFactor, + uint splitAmt, + uint slippageAmt + ) public + { + enterMarket(getCETHAddress()); + enterMarket(getCDAIAddress()); + (,,,,uint maxWithdraw,) = getCompStats(address(this), ctokenAddr, ctokenFactor); + uint ethToSwap = ethToFree < maxWithdraw ? ethToFree : maxWithdraw; + redeemEth(ethToSwap); + uint destAmt = SplitSwapInterface(getAddressSplitSwap()).ethToDaiSwap.value(ethToSwap)(splitAmt, slippageAmt); + repayDai(destAmt); + emit LogSaveDaiCompound(ethToSwap, destAmt); + } + + function leverage( + uint daiToBorrow, + address[] memory cTokenAddr, + uint[] memory ctokenFactor, + uint splitAmt, + uint slippageAmt + ) public + { + enterMarket(getCETHAddress()); + enterMarket(getCDAIAddress()); + (,,,uint borrowRemain,,) = getCompStats(address(this), cTokenAddr, ctokenFactor); + uint daiToSwap = getDaiRemainBorrow(borrowRemain); + daiToSwap = daiToSwap < daiToBorrow ? daiToSwap : daiToBorrow; + borrow(daiToSwap); + ERC20Interface(getAddressDAI()).approve(getAddressSplitSwap(), daiToSwap); + uint destAmt = SplitSwapInterface(getAddressSplitSwap()).daiToEthSwap(daiToSwap, splitAmt, slippageAmt); + mintCEth(destAmt); + emit LogLeverageDaiCompound(daiToSwap, destAmt); + } + +} + + +contract InstaCompDaiSave is CompoundSave { + + function() external payable {} + +} \ No newline at end of file diff --git a/contracts/ProxyLogics/MCD/InstaMcdSave.sol b/contracts/ProxyLogics/MCD/InstaMcdSave.sol new file mode 100644 index 0000000..b4659f5 --- /dev/null +++ b/contracts/ProxyLogics/MCD/InstaMcdSave.sol @@ -0,0 +1,647 @@ +pragma solidity ^0.5.8; + +interface GemLike { + function approve(address, uint) external; + function transfer(address, uint) external; + function transferFrom(address, address, uint) external; + function deposit() external payable; + function withdraw(uint) external; +} + +interface ManagerLike { + function cdpCan(address, uint, address) external view returns (uint); + function ilks(uint) external view returns (bytes32); + function owns(uint) external view returns (address); + function urns(uint) external view returns (address); + function vat() external view returns (address); + function open(bytes32, address) external returns (uint); + function give(uint, address) external; + function cdpAllow(uint, address, uint) external; + function urnAllow(address, uint) external; + function frob(uint, int, int) external; + function flux(uint, address, uint) external; + function move(uint, address, uint) external; + function exit( + address, + uint, + address, + uint + ) external; + function quit(uint, address) external; + function enter(address, uint) external; + function shift(uint, uint) external; +} + +interface VatLike { + function can(address, address) external view returns (uint); + function ilks(bytes32) external view returns (uint, uint, uint, uint, uint); + function dai(address) external view returns (uint); + function urns(bytes32, address) external view returns (uint, uint); + function frob( + bytes32, + address, + address, + address, + int, + int + ) external; + function hope(address) external; + function move(address, address, uint) external; + function gem(bytes32, address) external view returns (uint); + +} + +interface GemJoinLike { + function dec() external returns (uint); + function gem() external returns (GemLike); + function join(address, uint) external payable; + function exit(address, uint) external; +} + +interface DaiJoinLike { + function vat() external returns (VatLike); + function dai() external returns (GemLike); + function join(address, uint) external payable; + function exit(address, uint) external; +} + +interface JugLike { + function drip(bytes32) external returns (uint); +} + +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); +} + +interface InstaMcdAddress { + function manager() external view returns (address); + function dai() external view returns (address); + function daiJoin() external view returns (address); + function vat() external view returns (address); + function jug() external view returns (address); + function ethAJoin() external view returns (address); +} + + +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; + } + + function toInt(uint x) internal pure returns (int y) { + y = int(x); + require(y >= 0, "int-overflow"); + } + + function toRad(uint wad) internal pure returns (uint rad) { + rad = mul(wad, 10 ** 27); + } + +} + + +contract Helpers is DSMath { + + /** + * @dev get MakerDAO MCD Address contract + */ + function getMcdAddresses() public pure returns (address mcd) { + mcd = 0xF23196DF1C440345DE07feFbe556a5eF0dcD29F0; + } + + /** + * @dev get MakerDAO Oracle for ETH price + */ + function getOracleAddress() public pure returns (address oracle) { + oracle = 0x729D19f657BD0614b4985Cf1D82531c67569197B; + } + + /** + * @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 = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + } + + /** + * @dev get admin address + */ + function getAddressSplitSwap() public pure returns (address payable splitSwap) { + splitSwap = 0xc51a5024280d6AB2596e4aFFe1BDf6bDc2318da2; + } + + function getVaultStats(uint cup) internal view returns (uint ethCol, uint daiDebt, uint usdPerEth) { + address manager = InstaMcdAddress(getMcdAddresses()).manager(); + address urn = ManagerLike(manager).urns(cup); + bytes32 ilk = ManagerLike(manager).ilks(cup); + (ethCol, daiDebt) = VatLike(ManagerLike(manager).vat()).urns(ilk, urn); + (,uint rate,,,) = VatLike(ManagerLike(manager).vat()).ilks(ilk); + daiDebt = rmul(daiDebt, rate); + usdPerEth = uint(oracleInterface(getOracleAddress()).read()); + } + +} + + +contract MakerHelpers is Helpers { + + event LogLock(uint vaultId, uint amtETH, address owner); + event LogFree(uint vaultId, uint amtETH, address owner); + event LogDraw(uint vaultId, uint daiAmt, address owner); + event LogWipe(uint vaultId, uint daiAmt, address owner); + + function setAllowance(TokenInterface _token, address _spender) internal { + if (_token.allowance(address(this), _spender) != uint(-1)) { + _token.approve(_spender, uint(-1)); + } + } + + function _getDrawDart( + address vat, + address jug, + address urn, + bytes32 ilk, + uint wad + ) internal returns (int dart) + { + // Updates stability fee rate + uint rate = JugLike(jug).drip(ilk); + + // Gets DAI balance of the urn in the vat + uint dai = VatLike(vat).dai(urn); + + // If there was already enough DAI in the vat balance, just exits it without adding more debt + if (dai < mul(wad, RAY)) { + // Calculates the needed dart so together with the existing dai in the vat is enough to exit wad amount of DAI tokens + dart = toInt(sub(mul(wad, RAY), dai) / rate); + // This is neeeded due lack of precision. It might need to sum an extra dart wei (for the given DAI wad amount) + dart = mul(uint(dart), rate) < mul(wad, RAY) ? dart + 1 : dart; + } + } + + function _getWipeDart( + address vat, + uint dai, + address urn, + bytes32 ilk + ) internal view returns (int dart) + { + // Gets actual rate from the vat + (, uint rate,,,) = VatLike(vat).ilks(ilk); + // Gets actual art value of the urn + (, uint art) = VatLike(vat).urns(ilk, urn); + + // Uses the whole dai balance in the vat to reduce the debt + dart = toInt(dai / rate); + // Checks the calculated dart is not higher than urn.art (total debt), otherwise uses its value + dart = uint(dart) <= art ? - dart : - toInt(art); + } + + function joinDaiJoin(address urn, uint wad) internal { + address daiJoin = InstaMcdAddress(getMcdAddresses()).daiJoin(); + // Gets DAI from the user's wallet + DaiJoinLike(daiJoin).dai().transferFrom(msg.sender, address(this), wad); + // Approves adapter to take the DAI amount + DaiJoinLike(daiJoin).dai().approve(daiJoin, wad); + // Joins DAI into the vat + DaiJoinLike(daiJoin).join(urn, wad); + } + + function lock(uint cdpNum, uint wad) internal { + if (wad > 0) { + address ethJoin = InstaMcdAddress(getMcdAddresses()).ethAJoin(); + address manager = InstaMcdAddress(getMcdAddresses()).manager(); + + // Wraps ETH in WETH + GemJoinLike(ethJoin).gem().deposit.value(wad)(); + // Approves adapter to take the WETH amount + GemJoinLike(ethJoin).gem().approve(address(ethJoin), wad); + // Joins WETH collateral into the vat + GemJoinLike(ethJoin).join(address(this), wad); + // Locks WETH amount into the CDP + VatLike(ManagerLike(manager).vat()).frob( + ManagerLike(manager).ilks(cdpNum), + ManagerLike(manager).urns(cdpNum), + address(this), + address(this), + toInt(wad), + 0 + ); + // Sends ETH back to the user's wallet + emit LogLock( + cdpNum, + wad, + address(this) + ); + } + } + + function free(uint cdp, uint wad) internal { + if (wad > 0) { + address ethJoin = InstaMcdAddress(getMcdAddresses()).ethAJoin(); + address manager = InstaMcdAddress(getMcdAddresses()).manager(); + + // Unlocks WETH amount from the CDP + ManagerLike(manager).frob( + cdp, + -toInt(wad), + 0 + ); + // Moves the amount from the CDP urn to proxy's address + ManagerLike(manager).flux( + cdp, + address(this), + wad + ); + // Exits WETH amount to proxy address as a token + GemJoinLike(ethJoin).exit(address(this), wad); + // Converts WETH to ETH + GemJoinLike(ethJoin).gem().withdraw(wad); + // Sends ETH back to the user's wallet + + emit LogFree( + cdp, + wad, + address(this) + ); + } + } + + function draw(uint cdp, uint wad) internal { + if (wad > 0) { + address manager = InstaMcdAddress(getMcdAddresses()).manager(); + address jug = InstaMcdAddress(getMcdAddresses()).jug(); + address daiJoin = InstaMcdAddress(getMcdAddresses()).daiJoin(); + address urn = ManagerLike(manager).urns(cdp); + address vat = ManagerLike(manager).vat(); + bytes32 ilk = ManagerLike(manager).ilks(cdp); + // Generates debt in the CDP + ManagerLike(manager).frob( + cdp, + 0, + _getDrawDart( + vat, + jug, + urn, + ilk, + wad + ) + ); + // Moves the DAI amount (balance in the vat in rad) to proxy's address + ManagerLike(manager).move( + cdp, + address(this), + toRad(wad) + ); + // Allows adapter to access to proxy's DAI balance in the vat + if (VatLike(vat).can(address(this), address(daiJoin)) == 0) { + VatLike(vat).hope(daiJoin); + } + // Exits DAI to the user's wallet as a token + DaiJoinLike(daiJoin).exit(address(this), wad); + + emit LogDraw( + cdp, + wad, + address(this) + ); + } + } + + function wipe(uint cdp, uint wad) internal { + if (wad > 0) { + address manager = InstaMcdAddress(getMcdAddresses()).manager(); + address vat = ManagerLike(manager).vat(); + address urn = ManagerLike(manager).urns(cdp); + bytes32 ilk = ManagerLike(manager).ilks(cdp); + + address own = ManagerLike(manager).owns(cdp); + if (own == address(this) || ManagerLike(manager).cdpCan(own, cdp, address(this)) == 1) { + // Joins DAI amount into the vat + joinDaiJoin(urn, wad); + // Paybacks debt to the CDP + ManagerLike(manager).frob( + cdp, + 0, + _getWipeDart( + vat, + VatLike(vat).dai(urn), + urn, + ilk + ) + ); + } else { + // Joins DAI amount into the vat + joinDaiJoin(address(this), wad); + // Paybacks debt to the CDP + VatLike(vat).frob( + ilk, + urn, + address(this), + address(this), + 0, + _getWipeDart( + vat, + wad * RAY, + urn, + ilk + ) + ); + } + + emit LogWipe( + cdp, + wad, + address(this) + ); + + } + } + +} + + +contract GetDetails is MakerHelpers { + + function getMax(uint cdpID) public view returns (uint maxColToFree, uint maxDaiToDraw, uint ethInUSD) { + (uint ethCol, uint daiDebt, uint usdPerEth) = getVaultStats(cdpID); + 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) { + (uint ethCol, uint daiDebt, uint usdPerEth) = getVaultStats(cdpID); + (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 + ) + { + (uint ethCol, uint daiDebt, uint usdPerEth) = getVaultStats(cdpID); + (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 LogSaveVault( + uint vaultId, + uint srcETH, + uint destDAI + ); + + event LogLeverageVault( + uint vaultId, + uint srcDAI, + uint destETH + ); + + + function save( + uint cdpID, + uint colToSwap, + uint splitAmt, + uint slippageAmt + ) public + { + (uint ethCol, uint daiDebt, uint usdPerEth) = getVaultStats(cdpID); + 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); + uint finalDebt = sub(daiDebt, destAmt); + require(finalDebt >= 20*10**18 || finalDebt == 0, "Final Debt should be min 20Dai."); + wipe(cdpID, destAmt); + + emit LogSaveVault(cdpID, ethToSwap, destAmt); + } + + function leverage( + uint cdpID, + uint daiToSwap, + uint splitAmt, + uint slippageAmt + ) public + { + (uint ethCol, uint daiDebt, uint usdPerEth) = getVaultStats(cdpID); + 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 LogLeverageVault(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 InstaMcdSave is Save { + function() external payable {} +} \ No newline at end of file diff --git a/contracts/ProxyLogics/SplitSwap/SplitSwapMcd.sol b/contracts/ProxyLogics/SplitSwap/SplitSwapMcd.sol new file mode 100644 index 0000000..5f83884 --- /dev/null +++ b/contracts/ProxyLogics/SplitSwap/SplitSwapMcd.sol @@ -0,0 +1,378 @@ +pragma solidity ^0.5.8; + +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 = 0x2a1530C4C41db0B0b2bB646CB5Eb1A67b7158667; // Uniswap DAI exchange + address public kyberAddr = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; + address public ethAddr = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address public wethAddr = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public daiAddr = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + 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 {} + +} \ No newline at end of file