diff --git a/contracts/flash-mint/interface/IERC3156FlashBorrower.sol b/contracts/flash-mint/interface/IERC3156FlashBorrower.sol index 4109092..3392c71 100644 --- a/contracts/flash-mint/interface/IERC3156FlashBorrower.sol +++ b/contracts/flash-mint/interface/IERC3156FlashBorrower.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity >=0.6.12; +pragma solidity ^0.8.1; interface IERC3156FlashBorrower { /** diff --git a/contracts/flash-mint/interface/IERC3156FlashLender.sol b/contracts/flash-mint/interface/IERC3156FlashLender.sol index a510a57..8455b35 100644 --- a/contracts/flash-mint/interface/IERC3156FlashLender.sol +++ b/contracts/flash-mint/interface/IERC3156FlashLender.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity >=0.6.12; +pragma solidity ^0.8.1; import "./IERC3156FlashBorrower.sol"; diff --git a/contracts/interfaces/exchange/IExchange.sol b/contracts/interfaces/exchange/IExchange.sol index 8c2d668..f2fea55 100644 --- a/contracts/interfaces/exchange/IExchange.sol +++ b/contracts/interfaces/exchange/IExchange.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.7.0; +pragma solidity ^0.8.1; abstract contract IExchange { function swapDaiForToken( diff --git a/contracts/multiply/CdpData.sol b/contracts/multiply/CdpData.sol new file mode 100644 index 0000000..d84f8e1 --- /dev/null +++ b/contracts/multiply/CdpData.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.1; + +struct CdpData { + address gemJoin; + address payable fundsReceiver; + uint256 cdpId; + bytes32 ilk; + uint256 requiredDebt; + uint256 borrowCollateral; + uint256 withdrawCollateral; + uint256 withdrawDai; + uint256 depositDai; + uint256 depositCollateral; + bool skipFL; + string methodName; +} diff --git a/contracts/multiply/MultiplyProxyActions.sol b/contracts/multiply/MultiplyProxyActions.sol index 8a505d4..1434b33 100644 --- a/contracts/multiply/MultiplyProxyActions.sol +++ b/contracts/multiply/MultiplyProxyActions.sol @@ -27,6 +27,7 @@ import "../interfaces/mcd/IJug.sol"; import "../interfaces/mcd/IDaiJoin.sol"; import "../interfaces/exchange/IExchange.sol"; import "./ExchangeData.sol"; +import "./CdpData.sol"; import "../flash-mint/interface/IERC3156FlashBorrower.sol"; import "../flash-mint/interface/IERC3156FlashLender.sol"; @@ -34,21 +35,6 @@ import "../flash-mint/interface/IERC3156FlashLender.sol"; pragma solidity ^0.8.1; pragma abicoder v2; -struct CdpData { - address gemJoin; - address payable fundsReceiver; - uint256 cdpId; - bytes32 ilk; - uint256 requiredDebt; - uint256 borrowCollateral; - uint256 withdrawCollateral; - uint256 withdrawDai; - uint256 depositDai; - uint256 depositCollateral; - bool skipFL; - string methodName; -} - struct AddressRegistry { address jug; address manager; @@ -415,23 +401,6 @@ contract MultiplyProxyActions is IERC3156FlashBorrower { return ink; } - function _getWipeDart( - address vat, - uint256 dai, - address urn, - bytes32 ilk - ) internal view returns (int256 dart) { - // Gets actual rate from the vat - (, uint256 rate, , , ) = IVat(vat).ilks(ilk); - // Gets actual art value of the urn - (, uint256 art) = IVat(vat).urns(ilk, urn); - - // Uses the whole dai balance in the vat to reduce the debt - dart = toInt256(dai / rate); - // Checks the calculated dart is not higher than urn.art (total debt), otherwise uses its value - dart = uint256(dart) <= art ? -dart : -toInt256(art); - } - function _getWipeAllWad( address vat, address usr, @@ -452,6 +421,23 @@ contract MultiplyProxyActions is IERC3156FlashBorrower { wad = wad.mul(RAY) < rad ? wad + 1 : wad; } + function _getWipeDart( + address vat, + uint256 dai, + address urn, + bytes32 ilk + ) internal view returns (int256 dart) { + // Gets actual rate from the vat + (, uint256 rate, , , ) = IVat(vat).ilks(ilk); + // Gets actual art value of the urn + (, uint256 art) = IVat(vat).urns(ilk, urn); + + // Uses the whole dai balance in the vat to reduce the debt + dart = toInt256(dai / rate); + // Checks the calculated dart is not higher than urn.art (total debt), otherwise uses its value + dart = uint256(dart) <= art ? -dart : -toInt256(art); + } + function wipeAndFreeGem( address manager, address gemJoin, diff --git a/contracts/multiply_alternative/ActionsRunner.sol b/contracts/multiply_alternative/ActionsRunner.sol new file mode 100644 index 0000000..e61016f --- /dev/null +++ b/contracts/multiply_alternative/ActionsRunner.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.8.1; +import "./interfaces/ExternalInterfaces.sol"; +import "./../flash-mint/interface/IERC3156FlashBorrower.sol"; +import "./../flash-mint/interface/IERC3156FlashLender.sol"; + +struct FlashLoanData { + address lendedTokenAddress; + uint256 lendedTokenAmount; +} + +struct FlashLoanExecutionData { + address caller; + address token; + uint256 amount; + uint256 fee; +} + +abstract contract BaseAction { + function main(bytes calldata data, FlashLoanExecutionData memory executionData) external virtual; + + function beforeFlashLoan(bytes calldata data) external virtual returns (bytes memory); + + function afterFlashLoan(bytes calldata data) external virtual; + + function isFlashLoanRequired() external virtual returns (bool); +} + +contract FlashLoanProvider is IERC3156FlashBorrower { + address public immutable lender; + address public immutable self; + + constructor(address _lender) { + lender = _lender; + self = address(this); + } + + function onFlashLoan( + address caller, + address token, + uint256 amount, + uint256 fee, + bytes calldata params + ) public override returns (bytes32) { + (address action, bytes memory mainData) = abi.decode(params, (address, bytes)); + BaseAction(action).main(mainData, FlashLoanExecutionData(caller, token, amount, fee)); //here we do not mind change context since we changed it anyway + } + + function execute(address action, bytes memory actionsData) public { + (FlashLoanData memory flashLoanData, bytes memory mainData) = abi.decode( + actionsData, + (FlashLoanData, bytes) + ); + + bytes memory beforeCallData = abi.encodeWithSignature("beforeFlashLoan(bytes)", mainData); + (bool status, bytes memory step2Data) = address(action).delegatecall(beforeCallData); + require(status, "action/beforeFlashLoan-failed"); + + IERC3156FlashLender(lender).flashLoan( + IERC3156FlashBorrower(self), + flashLoanData.lendedTokenAddress, + flashLoanData.lendedTokenAmount, + abi.encode(action, step2Data) + ); + + bytes memory afterCallData = abi.encodeWithSignature("afterFlashLoan(bytes)", mainData); + (status, ) = address(action).delegatecall(afterCallData); + require(status, "action/afterFlashLoan-failed"); + } +} + +contract Runner { + ServiceRegistryLike public immutable registry; + FlashLoanProvider public immutable flashLoanProvider; + + constructor(address _reg, address _flProvider) { + registry = ServiceRegistryLike(_reg); + flashLoanProvider = FlashLoanProvider(_flProvider); + } + + /* + actionName - key for stored in serviceRegistry address of action implementation, for example MCLOSE_TO_DAI, GCLOSE_TO_DAI + actionData - abi.encode(flashLoanData, mainData) + where flashLoanData are information for flash loan about asset and amount to borrow + where mainData is action-specific bytes used in beforeFlashLoan, main, afterFlashLoan that then Adtion internally decodes to whatever it uses + */ + function executeAction(string calldata actionName, bytes calldata actionData) external { + BaseAction action = BaseAction(registry.getRegisteredService(actionName)); + + bool useFlashLoan = action.isFlashLoanRequired(); + if (useFlashLoan) { + bytes memory flashLoanCallData = abi.encodeWithSignature( + "execute(address,bytes)", + abi.encode(address(action), actionData) + ); + (bool status, ) = address(flashLoanProvider).delegatecall(flashLoanCallData); + require(status, "runner/flashloan-failed"); + } else { + bytes memory actionDelegateData = abi.encodeWithSignature("main(bytes)", actionData); + (bool status, ) = address(action).delegatecall(actionDelegateData); + require(status, "runner/action-failed"); + } + } +} diff --git a/contracts/multiply_alternative/MakerCalculations.sol b/contracts/multiply_alternative/MakerCalculations.sol new file mode 100644 index 0000000..d7abe32 --- /dev/null +++ b/contracts/multiply_alternative/MakerCalculations.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.8.1; +import "./../utils/SafeMath.sol"; +import "./../interfaces/IERC20.sol"; +import "./../interfaces/mcd/IVat.sol"; +import "./../interfaces/mcd/IManager.sol"; +import "./../interfaces/mcd/IJoin.sol"; + +//TODO: This should be library "using MakerMath for uint256" +//TODO: Get rid of SafeMath since new compiler do safeMath out of the box +contract MakerMath { + using SafeMath for uint256; + + function convertTo18(address gemJoin, uint256 amt) internal view returns (uint256 wad) { + // For those collaterals that have less than 18 decimals precision we need to do the conversion before passing to frob function + // Adapters will automatically handle the difference of precision + wad = amt.mul(10**(18 - IJoin(gemJoin).dec())); + } + + function toInt256(uint256 x) internal pure returns (int256 y) { + y = int256(x); + require(y >= 0, "int256-overflow"); + } +} + +//TODO:This eventually should be library not inheritance, it can be used as "using MakerTools for IVat" +contract MakerCalculations is MakerMath { + uint256 constant RAY = 10**27; + using SafeMath for uint256; + + function _getWipeDart( + address vat, + uint256 dai, + address urn, + bytes32 ilk + ) internal view returns (int256 dart) { + // Gets actual rate from the vat + (, uint256 rate, , , ) = IVat(vat).ilks(ilk); + // Gets actual art value of the urn + (, uint256 art) = IVat(vat).urns(ilk, urn); + + // Uses the whole dai balance in the vat to reduce the debt + dart = toInt256(dai / rate); + // Checks the calculated dart is not higher than urn.art (total debt), otherwise uses its value + dart = uint256(dart) <= art ? -dart : -toInt256(art); + } + + function _getWipeAllWad( + address vat, + address usr, + address urn, + bytes32 ilk + ) internal view returns (uint256 wad) { + // Gets actual rate from the vat + (, uint256 rate, , , ) = IVat(vat).ilks(ilk); + // Gets actual art value of the urn + (, uint256 art) = IVat(vat).urns(ilk, urn); + // Gets actual dai amount in the urn + uint256 dai = IVat(vat).dai(usr); + + uint256 rad = art.mul(rate).sub(dai); + wad = rad / RAY; + + // If the rad precision has some dust, it will need to request for 1 extra wad wei + wad = wad.mul(RAY) < rad ? wad + 1 : wad; + } +} diff --git a/contracts/multiply_alternative/MakerTools.sol b/contracts/multiply_alternative/MakerTools.sol new file mode 100644 index 0000000..47efa59 --- /dev/null +++ b/contracts/multiply_alternative/MakerTools.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.8.1; +import "./MakerCalculations.sol"; +import "./interfaces/ExternalInterfaces.sol"; +import "./../utils/SafeMath.sol"; +import "./../interfaces/IERC20.sol"; +import "./../interfaces/mcd/IVat.sol"; +import "./../interfaces/mcd/IManager.sol"; +import "./../interfaces/mcd/IDaiJoin.sol"; +import "./../interfaces/mcd/IJoin.sol"; +import "./../../contracts/interfaces/exchange/IExchange.sol"; + +//TODO:This eventually should be library not inheritance +contract MakerTools is MakerCalculations { + address public immutable daijoin; + IERC20 public immutable DAI; + using SafeMath for uint256; + + constructor(address _daiJoin, address _dai) { + daijoin = _daiJoin; + DAI = IERC20(_dai); + } + + function wipeAndFreeGem( + address manager, + address gemJoin, + uint256 cdp, + uint256 borrowedDai, + uint256 collateralDraw + ) internal { + address vat = IManager(manager).vat(); + address urn = IManager(manager).urns(cdp); + bytes32 ilk = IManager(manager).ilks(cdp); + + IERC20(DAI).approve(daijoin, borrowedDai); + IDaiJoin(daijoin).join(urn, borrowedDai); + + uint256 wadC = convertTo18(gemJoin, collateralDraw); + + IManager(manager).frob(cdp, -toInt256(wadC), _getWipeDart(vat, IVat(vat).dai(urn), urn, ilk)); + + IManager(manager).flux(cdp, address(this), wadC); + IJoin(gemJoin).exit(address(this), collateralDraw); + } +} diff --git a/contracts/multiply_alternative/actions/CloseToDai.sol b/contracts/multiply_alternative/actions/CloseToDai.sol new file mode 100644 index 0000000..f9dd645 --- /dev/null +++ b/contracts/multiply_alternative/actions/CloseToDai.sol @@ -0,0 +1,141 @@ +pragma solidity ^0.8.1; +import "./../interfaces/ExternalInterfaces.sol"; +import "./../ActionsRunner.sol"; +import "./../MakerTools.sol"; +import "../../multiply/ExchangeData.sol"; +import "../../multiply/CdpData.sol"; + +contract CloseToDai is + BaseAction, + MakerTools /* MakerTools inheritance should be removed and functionality should be abstracted or as library or as operations */ +{ + address public immutable manager; + address public immutable exchange; + ServiceRegistryLike public immutable registry; + using SafeMath for uint256; + + constructor( + address _reg, + address _manager, + address _exchange, + address _dai, + address _dajJoin + ) MakerTools(_dajJoin, _dai) { + manager = _manager; + exchange = _exchange; + registry = ServiceRegistryLike(_reg); + } + + function main(bytes calldata params, FlashLoanExecutionData memory executionData) + external + override + { + (ExchangeData memory exchangeData, CdpData memory cdpData) = abi.decode( + params, + (ExchangeData, CdpData) + ); + + require( + msg.sender == address(registry.getRegisteredService("MAKER_LENDER")), + "mpa-untrusted-lender" + ); + + uint256 borrowedDaiAmount = executionData.amount.add(executionData.fee); + emit FLData(DAI.balanceOf(address(this)).sub(cdpData.depositDai), borrowedDaiAmount); + + _closeWithdrawDai(exchangeData, cdpData, borrowedDaiAmount, cdpData.borrowCollateral); + + require( + cdpData.requiredDebt.add(cdpData.depositDai) <= DAI.balanceOf(address(this)), + "mpa-receive-requested-amount-mismatch" + ); + } + + function _closeWithdrawDai( + ExchangeData memory exchangeData, + CdpData memory cdpData, + uint256 borrowedDaiAmount, + uint256 ink + ) private { + IExchange exchangeInstance = IExchange(exchange); + address gemAddress = address(IJoin(cdpData.gemJoin).gem()); + + wipeAndFreeGem(manager, cdpData.gemJoin, cdpData.cdpId, cdpData.requiredDebt, ink); + + require( + IERC20(exchangeData.fromTokenAddress).approve( + address(exchange), + IERC20(gemAddress).balanceOf(address(this)) + ), + "MPA / Could not approve Exchange for Token" + ); + + exchangeInstance.swapTokenForDai( + exchangeData.fromTokenAddress, + ink, + exchangeData.minToTokenAmount, + exchangeData.exchangeAddress, + exchangeData._exchangeCalldata + ); + + uint256 daiLeft = IERC20(DAI).balanceOf(address(this)).sub(borrowedDaiAmount); + + if (daiLeft > 0) { + IERC20(DAI).transfer(cdpData.fundsReceiver, daiLeft); + } + uint256 collateralLeft = IERC20(gemAddress).balanceOf(address(this)); + /* + if (collateralLeft > 0) { + _withdrawGem(cdpData.gemJoin, cdpData.fundsReceiver, collateralLeft); + }*/ + emit MultipleActionCalled( + cdpData.methodName, + cdpData.cdpId, + exchangeData.minToTokenAmount, + exchangeData.toTokenAmount, + collateralLeft, + daiLeft + ); + } + + function beforeFlashLoan(bytes calldata data) external override returns (bytes memory) { + (ExchangeData memory exchangeData, CdpData memory cdpData) = abi.decode( + data, + (ExchangeData, CdpData) + ); + + cdpData.ilk = IJoin(cdpData.gemJoin).ilk(); + + address urn = IManager(manager).urns(cdpData.cdpId); + address vat = IManager(manager).vat(); + + uint256 wadD = _getWipeAllWad(vat, urn, urn, cdpData.ilk); + cdpData.requiredDebt = wadD; + + bytes memory paramsData = abi.encode(exchangeData, cdpData); + + return paramsData; + } + + function afterFlashLoan(bytes calldata data) external override { + (ExchangeData memory exchangeData, CdpData memory cdpData) = abi.decode( + data, + (ExchangeData, CdpData) + ); + IManager(manager).cdpAllow(cdpData.cdpId, registry.getRegisteredService("ACTION_RUNNER"), 0); + } + + function isFlashLoanRequired() external pure override returns (bool) { + return true; + } + + event FLData(uint256 borrowed, uint256 due); + event MultipleActionCalled( + string methodName, + uint256 indexed cdpId, + uint256 swapMinAmount, + uint256 swapOptimistAmount, + uint256 collateralLeft, + uint256 daiLeft + ); +} diff --git a/contracts/multiply_alternative/interfaces/ExternalInterfaces.sol b/contracts/multiply_alternative/interfaces/ExternalInterfaces.sol new file mode 100644 index 0000000..55f20ba --- /dev/null +++ b/contracts/multiply_alternative/interfaces/ExternalInterfaces.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.8.1; + +abstract contract ServiceRegistryLike { + function isTrusted(address testedAddress) external view virtual returns (bool); + + function getRegisteredService(string memory serviceName) external view virtual returns (address); +} diff --git a/package.json b/package.json index 84243c5..c7016d9 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "deploy": "cmd/run_deploy", "test": "cmd/run_test", "verify": "cmd/run_verify", - "lint": "./node_modules/.bin/solhint -f table contracts/**/*.sol", - "lint:fix": "./node_modules/.bin/solhint --fix constracts/**/*.sol", - "format": "./node_modules/.bin/prettier -c contracts/**/*.sol test/**/*.ts", - "format:fix": "./node_modules/.bin/prettier -w contracts/**/*.sol test/**/*.ts", + "lint": "./node_modules/.bin/solhint -f table contracts/**/*.sol contracts/**/**/*.sol", + "lint:fix": "./node_modules/.bin/solhint --fix contracts/**/*.sol contracts/**/**/*.sol", + "format": "./node_modules/.bin/prettier -c contracts/**/*.sol contracts/**/**/*.sol test/**/*.ts", + "format:fix": "./node_modules/.bin/prettier -w contracts/**/*.sol contracts/**/**/*.sol test/**/*.ts", "flatten-all": "sol-merger@3.1.0 \"./contracts/*.sol\" ./build", "flatten": "npx sol-merger@3.1.0" },