From ee460c94f710165b6d51cea892f1dc59734af2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20W=C5=82odarczyk?= Date: Fri, 28 Jan 2022 07:12:26 +0100 Subject: [PATCH] Flash loan action contract --- contracts/actions/OperationData.sol | 9 +++ contracts/actions/OperationRunner.sol | 58 +++++++++++----- contracts/actions/deposit.sol | 42 ++++-------- contracts/actions/flashloan.sol | 84 +++++++++++++++++++---- contracts/actions/openVault.sol | 10 +-- test/actions-poc-test.ts | 57 ++++++++++++--- test/common/utils/mcd-deployment.utils.ts | 9 +++ 7 files changed, 194 insertions(+), 75 deletions(-) create mode 100644 contracts/actions/OperationData.sol diff --git a/contracts/actions/OperationData.sol b/contracts/actions/OperationData.sol new file mode 100644 index 0000000..ab3d5f4 --- /dev/null +++ b/contracts/actions/OperationData.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.7.6; +pragma abicoder v2; + +struct Operation { + string name; + bytes[][] callData; + bytes32[] actionIds; +} diff --git a/contracts/actions/OperationRunner.sol b/contracts/actions/OperationRunner.sol index c0cfdc3..281c41c 100644 --- a/contracts/actions/OperationRunner.sol +++ b/contracts/actions/OperationRunner.sol @@ -7,33 +7,43 @@ import "../interfaces/mcd/IManager.sol"; import "./ActionBase.sol"; import "hardhat/console.sol"; import "../ServiceRegistry.sol"; - -struct Operation { - string name; - bytes[][] callData; - bytes32[] actionIds; - address serviceRegistryAddr; -} +import "./OperationData.sol"; contract OperationRunner { + address public constant serviceRegistryAddr = 0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5; + function executeOperation(Operation memory operation) public payable { _executeActions(operation); } + function _executeActionsAfterFlashLoan(Operation memory operation, bytes32 _flashloanAmount) + public + payable + { + bytes32[] memory returnValues = new bytes32[](operation.actionIds.length); + returnValues[0] = _flashloanAmount; + + for (uint256 i = 2; i < operation.actionIds.length; ++i) { + console.log("RUN THIRD OPERATION"); + returnValues[i] = _executeAction(operation, i, returnValues); + } + } + function _executeActions(Operation memory operation) internal { - // address firstActionAddr = ServiceRegistry(operation.serviceRegistryAddr) - // .getServiceAddress(operation.actionIds[0]); + address firstActionAddr = ServiceRegistry(serviceRegistryAddr).getServiceAddress( + operation.actionIds[1] + ); bytes32[] memory returnValues = new bytes32[](operation.actionIds.length); - // if (isFlashLoanAction(firstActionAddr)) { - // executeFlashloan(operation, firstActionAddr, returnValues); - // } else { - for (uint256 i = 0; i < operation.actionIds.length; ++i) { - returnValues[i] = _executeAction(operation, i, returnValues); - // _executeAction(operation, i); + if (isFlashLoanAction(firstActionAddr)) { + returnValues[0] = _executeAction(operation, 0, returnValues); + returnValues[1] = _executeFlashLoan(operation, firstActionAddr, returnValues); + } else { + for (uint256 i = 0; i < operation.actionIds.length; ++i) { + returnValues[i] = _executeAction(operation, i, returnValues); + } } - // } } function _executeAction( @@ -41,7 +51,7 @@ contract OperationRunner { uint256 _index, bytes32[] memory returnValues ) internal returns (bytes32 response) { - address actionAddress = ServiceRegistry(operation.serviceRegistryAddr).getServiceAddress( + address actionAddress = ServiceRegistry(serviceRegistryAddr).getServiceAddress( operation.actionIds[_index] ); @@ -52,6 +62,20 @@ contract OperationRunner { return ""; } + function _executeFlashLoan( + Operation memory operation, + address _flashloanActionAddr, + bytes32[] memory _returnValues + ) internal returns (bytes32) { + bytes memory operationFL = abi.encode(operation); + operation.callData[1][operation.callData[1].length - 1] = operationFL; + + _flashloanActionAddr.delegatecall( + abi.encodeWithSignature("executeAction(bytes[])", operation.callData[1]) + ); + return bytes32(0); + } + function isFlashLoanAction(address actionAddr) internal pure returns (bool) { return ActionBase(actionAddr).actionType() == uint8(ActionBase.ActionType.FLASHLOAN); } diff --git a/contracts/actions/deposit.sol b/contracts/actions/deposit.sol index 8bde72b..5e0ec29 100644 --- a/contracts/actions/deposit.sol +++ b/contracts/actions/deposit.sol @@ -8,43 +8,31 @@ import "./ActionBase.sol"; import "hardhat/console.sol"; contract Deposit is ActionBase { - function executeAction(bytes[] memory _callData) - public - payable - override - returns ( - // ) public payable virtual override returns (bytes32) { - bytes32 - ) - { - (address joinAddr, address mcdManager) = parseInputs(_callData); - // address joinAddr = 0x2F0b23f53734252Bda2277357e97e1517d6B042A; - // address mcdManager = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; - - // // joinAddr = _parseParamAddr(joinAddr, _paramMapping[0], _subData, _returnValues); - - console.log("address this", address(this)); - - uint256 newVaultId = _mcdOpen(joinAddr, mcdManager); - - return bytes32(newVaultId); + function executeAction(bytes[] memory _callData) public payable override returns (bytes32) { + console.log("TODO EXECUTE DEPOSIT"); } function actionType() public pure override returns (uint8) { return uint8(ActionType.DEFAULT); } - function _mcdOpen(address _joinAddr, address _mcdManager) internal returns (uint256 vaultId) { - bytes32 ilk = IJoin(_joinAddr).ilk(); - vaultId = IManager(_mcdManager).open(ilk, address(this)); - } + function _deposit() internal returns (uint256 vaultId) {} function parseInputs(bytes[] memory _callData) internal pure - returns (address joinAddr, address mcdManager) + returns ( + uint256 vaultId, + uint256 amount, + address joinAddr, + address from, + address mcdManager + ) { - joinAddr = abi.decode(_callData[0], (address)); - mcdManager = abi.decode(_callData[1], (address)); + vaultId = abi.decode(_callData[0], (uint256)); + amount = abi.decode(_callData[1], (uint256)); + joinAddr = abi.decode(_callData[2], (address)); + from = abi.decode(_callData[3], (address)); + mcdManager = abi.decode(_callData[4], (address)); } } diff --git a/contracts/actions/flashloan.sol b/contracts/actions/flashloan.sol index f6c2db6..ae1fe73 100644 --- a/contracts/actions/flashloan.sol +++ b/contracts/actions/flashloan.sol @@ -2,25 +2,83 @@ pragma solidity >=0.7.6; pragma abicoder v2; -import "../interfaces/mcd/IJoin.sol"; -import "../interfaces/mcd/IManager.sol"; import "./ActionBase.sol"; +import { IERC20 } from "../interfaces/IERC20.sol"; +import "../flash-mint/interface/IERC3156FlashBorrower.sol"; +import "../flash-mint/interface/IERC3156FlashLender.sol"; +import "../ServiceRegistry.sol"; +import "./OperationData.sol"; +import "./OperationRunner.sol"; + import "hardhat/console.sol"; -contract Flashloan is ActionBase { +contract FlashLoan is ActionBase, IERC3156FlashBorrower { + address public constant serviceRegistryAddr = 0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5; + bytes32 constant FLASH_LOAN = keccak256("FLASH_LOAN"); + bytes32 constant FLASH_LOAN_LENDER = keccak256("FLASH_LOAN_LENDER"); + bytes32 constant OPERATION_RUNNER = keccak256("OPERATION_RUNNER"); + + address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + function actionType() public pure override returns (uint8) { return uint8(ActionType.FLASHLOAN); } - function executeAction(bytes[] memory _callData) - public - payable - override - returns ( - // ) public payable virtual override returns (bytes32) { - bytes32 - ) - { - return ""; + // beforeFlashloan + function executeAction(bytes[] memory _callData) public payable override returns (bytes32) { + bytes memory operation = _callData[1]; + _beforeFlashLoan(1000000, operation); + _afterFlashLoan(); + return bytes32(0); + } + + function _beforeFlashLoan(uint256 amount, bytes memory operation) internal { + address flashLoanLender = ServiceRegistry(serviceRegistryAddr).getServiceAddress( + FLASH_LOAN_LENDER + ); + address flashLoanAction = ServiceRegistry(serviceRegistryAddr).getServiceAddress(FLASH_LOAN); + + IERC3156FlashLender(flashLoanLender).flashLoan( + IERC3156FlashBorrower(flashLoanAction), + DAI, + amount, + operation + ); + } + + function duringFlashLoan( + Operation memory operation, + uint256 _amount, + uint256 _fee + ) public payable { + uint256 balance = IERC20(DAI).balanceOf(address(this)); + + uint256 paybackAmount = _amount + _fee; // todo: safe math + address operationRunnerAddr = ServiceRegistry(serviceRegistryAddr).getServiceAddress( + OPERATION_RUNNER + ); + + OperationRunner(operationRunnerAddr)._executeActionsAfterFlashLoan( + operation, + bytes32(paybackAmount) + ); + } + + function _afterFlashLoan() internal { + //Todo: revoke cdpAllow + } + + function onFlashLoan( + address _initiator, + address _token, + uint256 _amount, + uint256 _fee, + bytes calldata params + ) external override returns (bytes32) { + Operation memory operation = abi.decode(params, (Operation)); + + duringFlashLoan(operation, _amount, _fee); + + return keccak256("ERC3156FlashBorrower.onFlashLoan"); } } diff --git a/contracts/actions/openVault.sol b/contracts/actions/openVault.sol index 65a14bc..a45df99 100644 --- a/contracts/actions/openVault.sol +++ b/contracts/actions/openVault.sol @@ -9,12 +9,9 @@ import "hardhat/console.sol"; contract OpenVault is ActionBase { function executeAction(bytes[] memory _callData) public payable override returns (bytes32) { - // (address joinAddr, address mcdManager) = parseInputs(_callData); + (address joinAddr, address mcdManager) = parseInputs(_callData); - address joinAddr = 0x2F0b23f53734252Bda2277357e97e1517d6B042A; - address mcdManager = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; - - uint256 newVaultId = _mcdOpen(joinAddr, mcdManager); + uint256 newVaultId = _openVault(joinAddr, mcdManager); return bytes32(newVaultId); } @@ -23,14 +20,13 @@ contract OpenVault is ActionBase { return uint8(ActionType.DEFAULT); } - function _mcdOpen(address _joinAddr, address _mcdManager) internal returns (uint256 vaultId) { + function _openVault(address _joinAddr, address _mcdManager) internal returns (uint256 vaultId) { bytes32 ilk = IJoin(_joinAddr).ilk(); vaultId = IManager(_mcdManager).open(ilk, address(this)); } function parseInputs(bytes[] memory _callData) internal - pure returns (address joinAddr, address mcdManager) { joinAddr = abi.decode(_callData[0], (address)); diff --git a/test/actions-poc-test.ts b/test/actions-poc-test.ts index 29bb1cc..9c90343 100644 --- a/test/actions-poc-test.ts +++ b/test/actions-poc-test.ts @@ -22,6 +22,7 @@ import { import { balanceOf } from './utils' import ERC20ABI from '../abi/IERC20.json' +import CDPManagerABI from '../abi/external/dss-cdp-manager.json' import { getVaultInfo } from './common/utils/mcd.utils' import { expectToBe, expectToBeEqual } from './common/utils/test.utils' import { one } from './common/cosntants' @@ -68,7 +69,6 @@ describe('Multiply Proxy Action with Mocked Exchange', async () => { system = await deploySystem(provider, signer, true) - // exchangeDataMock = { // to: system.exchangeInstance.address, // data: 0, @@ -95,22 +95,50 @@ describe('Multiply Proxy Action with Mocked Exchange', async () => { }) it(`should open vault with required collateralisation ratio`, async () => { - const callData1 = ethers.utils.defaultAbiCoder.encode(["address"], [MAINNET_ADDRESSES.MCD_JOIN_ETH_A]) - const callData2 = ethers.utils.defaultAbiCoder.encode(["address"], [MAINNET_ADDRESSES.CDP_MANAGER]) - const openVaultCalldata = system.actionOpenVault.interface.encodeFunctionData("executeAction", [[callData1, callData2]]); + const mcdJoinEth = ethers.utils.defaultAbiCoder.encode(["address"], [MAINNET_ADDRESSES.MCD_JOIN_ETH_A]) + const cdpManager = ethers.utils.defaultAbiCoder.encode(["address"], [MAINNET_ADDRESSES.CDP_MANAGER]) + const operationRunnerHashName = await system.serviceRegistry.getServiceNameHash('OPERATION_RUNNER'); const openVaultHashName = await system.serviceRegistry.getServiceNameHash('OPEN_VAULT'); + const flashLoanHashName = await system.serviceRegistry.getServiceNameHash('FLASH_LOAN'); + const depositHashName = await system.serviceRegistry.getServiceNameHash('DEPOSIT'); + const flashLoanLenderHashName = await system.serviceRegistry.getServiceNameHash('FLASH_LOAN_LENDER'); + + const FMM = "0x1EB4CF3A948E7D72A198fe073cCb8C7a948cD853"; // Maker Flash Mint Module + + await system.serviceRegistry.addNamedService( + operationRunnerHashName, + system.operationRunner.address, + ) await system.serviceRegistry.addNamedService( openVaultHashName, system.actionOpenVault.address, ) - + await system.serviceRegistry.addNamedService( + flashLoanHashName, + system.actionFlashLoan.address, + ) + await system.serviceRegistry.addNamedService( + depositHashName, + system.actionDeposit.address, + ) + await system.serviceRegistry.addNamedService( + flashLoanLenderHashName, + FMM, + ) + const dsproxy_calldata = system.operationRunner.interface.encodeFunctionData("executeOperation", [{ - name: 'openVaultOperation', - callData: [[openVaultCalldata],[openVaultCalldata]], - actionIds: [openVaultHashName, openVaultHashName], - serviceRegistryAddr: system.serviceRegistry.address, + name: 'openDepositDrawDebtOperation', + callData: [ + [mcdJoinEth, cdpManager], + [1000000,0], + [0, 10000, mcdJoinEth, system.userProxyAddress, cdpManager ] + ], + actionIds: [ + openVaultHashName, + flashLoanHashName, + depositHashName], }] ) @@ -120,10 +148,17 @@ describe('Multiply Proxy Action with Mocked Exchange', async () => { gasLimit: 8500000, gasPrice: 1000000000, }) - const lastCDP = await getLastCDP(provider, signer, system.userProxyAddress) - console.log('LAST CDP', lastCDP ); + + const cdpManagerContract = new ethers.Contract(MAINNET_ADDRESSES.CDP_MANAGER, CDPManagerABI, provider).connect( + signer, + ) + + const vaultOwner = await cdpManagerContract.owns(lastCDP.id); + + expectToBeEqual(vaultOwner, system.userProxyAddress) + }) }) }) diff --git a/test/common/utils/mcd-deployment.utils.ts b/test/common/utils/mcd-deployment.utils.ts index d7da3d1..aaccf21 100644 --- a/test/common/utils/mcd-deployment.utils.ts +++ b/test/common/utils/mcd-deployment.utils.ts @@ -354,6 +354,8 @@ export interface DeployedSystemInfo { } guni: Contract actionOpenVault: Contract + actionDeposit: Contract + actionFlashLoan: Contract operationRunner: Contract serviceRegistry: Contract } @@ -436,6 +438,13 @@ export async function deploySystem( const serviceRegistry = await ServiceRegistry.deploy([0]) deployedContracts.serviceRegistry = await serviceRegistry.deployed() + const ActionFlashLoan = await ethers.getContractFactory('FlashLoan', signer) + const actionFlashLoan = await ActionFlashLoan.deploy() + deployedContracts.actionFlashLoan = await actionFlashLoan.deployed() + + const ActionDeposit = await ethers.getContractFactory('Deposit', signer) + const actionDeposit = await ActionDeposit.deploy() + deployedContracts.actionDeposit = await actionDeposit.deployed() return deployedContracts as DeployedSystemInfo }