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"
},