diff --git a/contracts/optimism/connectors/aave/v3-import-permit/events.sol b/contracts/optimism/connectors/aave/v3-import-permit/events.sol index 10a93d70..75d3c8bb 100644 --- a/contracts/optimism/connectors/aave/v3-import-permit/events.sol +++ b/contracts/optimism/connectors/aave/v3-import-permit/events.sol @@ -12,4 +12,14 @@ contract Events { uint256[] supplyAmts, uint256[] borrowAmts ); + event LogAaveV3ImportWithPermitAndCollateral( + address indexed user, + address[] atokens, + string[] supplyIds, + string[] borrowIds, + uint256[] flashLoanFees, + uint256[] supplyAmts, + uint256[] borrowAmts, + bool[] enableCollateral + ); } diff --git a/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol b/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol index 61b1687d..3f1a1917 100644 --- a/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol +++ b/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol @@ -279,6 +279,34 @@ contract AaveHelpers is Helper { } } + function _TransferAtokensWithCollateral( + uint256 _length, + AaveInterface aave, + ATokenInterface[] memory atokenContracts, + uint256[] memory amts, + address[] memory tokens, + bool[] memory colEnable, + address userAccount + ) internal { + for (uint256 i = 0; i < _length; i++) { + if (amts[i] > 0) { + uint256 _amt = amts[i]; + require( + atokenContracts[i].transferFrom( + userAccount, + address(this), + _amt + ), + "allowance?" + ); + + if (!getIsColl(tokens[i], address(this))) { + aave.setUserUseReserveAsCollateral(tokens[i], colEnable[i]); + } + } + } + } + function _BorrowVariable( uint256 _length, AaveInterface aave, diff --git a/contracts/optimism/connectors/aave/v3-import-permit/main.sol b/contracts/optimism/connectors/aave/v3-import-permit/main.sol index 17fd9a6c..01c3a510 100644 --- a/contracts/optimism/connectors/aave/v3-import-permit/main.sol +++ b/contracts/optimism/connectors/aave/v3-import-permit/main.sol @@ -104,6 +104,105 @@ contract AaveV3ImportPermitResolver is AaveHelpers { ); } + function _importAaveWithCollateral( + address userAccount, + ImportInputData memory inputData, + SignedPermits memory permitData, + bool[] memory enableCollateral + ) internal returns (string memory _eventName, bytes memory _eventParam) { + require( + AccountInterface(address(this)).isAuth(userAccount), + "user-account-not-auth" + ); + + require(inputData.supplyTokens.length > 0, "0-length-not-allowed"); + require( + enableCollateral.length == inputData.supplyTokens.length, + "supplytokens-enableCol-len-not-same" + ); + + ImportData memory data; + + AaveInterface aave = AaveInterface(aaveProvider.getPool()); + + data = getBorrowAmounts(userAccount, aave, inputData, data); + data = getSupplyAmounts(userAccount, inputData, data); + + // payback borrowed amount; + _PaybackStable( + data._borrowTokens.length, + aave, + data._borrowTokens, + data.stableBorrowAmts, + userAccount + ); + _PaybackVariable( + data._borrowTokens.length, + aave, + data._borrowTokens, + data.variableBorrowAmts, + userAccount + ); + + //permit this address to transfer aTokens + _PermitATokens( + userAccount, + data.aTokens, + data._supplyTokens, + permitData.v, + permitData.r, + permitData.s, + permitData.expiry + ); + + // transfer atokens to this address; + _TransferAtokensWithCollateral( + data._supplyTokens.length, + aave, + data.aTokens, + data.supplyAmts, + data._supplyTokens, + enableCollateral, + userAccount + ); + + // borrow assets after migrating position + if (data.convertStable) { + _BorrowVariable( + data._borrowTokens.length, + aave, + data._borrowTokens, + data.totalBorrowAmtsWithFee + ); + } else { + _BorrowStable( + data._borrowTokens.length, + aave, + data._borrowTokens, + data.stableBorrowAmtsWithFee + ); + _BorrowVariable( + data._borrowTokens.length, + aave, + data._borrowTokens, + data.variableBorrowAmtsWithFee + ); + } + + _eventName = "LogAaveV3ImportWithPermitAndCollateral(address,bool,address[],address[],uint256[],uint256[],uint256[],uint256[],bool[])"; + _eventParam = abi.encode( + userAccount, + inputData.convertStable, + inputData.supplyTokens, + inputData.borrowTokens, + inputData.flashLoanFees, + data.supplyAmts, + data.stableBorrowAmts, + data.variableBorrowAmts, + enableCollateral + ); + } + /** * @dev Import aave V3 position . * @notice Import EOA's aave V3 position to DSA's aave v3 position @@ -126,6 +225,32 @@ contract AaveV3ImportPermitResolver is AaveHelpers { permitData ); } + + /** + * @dev Import aave V3 position (with collateral). + * @notice Import EOA's aave V3 position to DSA's aave v3 position + * @param userAccount The address of the EOA from which aave position will be imported + * @param inputData The struct containing all the neccessary input data + * @param permitData The struct containing signed permit data like v,r,s,expiry + * @param enableCollateral The boolean array to enable selected collaterals in the imported position + */ + function importAaveWithCollateral( + address userAccount, + ImportInputData memory inputData, + SignedPermits memory permitData, + bool[] memory enableCollateral + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + (_eventName, _eventParam) = _importAaveWithCollateral( + userAccount, + inputData, + permitData, + enableCollateral + ); + } } contract ConnectV2AaveV3ImportPermitOptimism is AaveV3ImportPermitResolver { diff --git a/test/optimism/aave/aaveV3-import-test.ts b/test/optimism/aave/aaveV3-import-test.ts index 21b1a3ec..6df90862 100644 --- a/test/optimism/aave/aaveV3-import-test.ts +++ b/test/optimism/aave/aaveV3-import-test.ts @@ -353,5 +353,112 @@ describe("Import Aave v3 Position for Optimism", function () { new BigNumber(10).multipliedBy(1e18).toString() ); }); + + describe("check user AAVE position", async () => { + it("Should create Aave v3 position of DAI(collateral), and USDC(debt)", async () => { + await token.connect(signer).transfer(wallet0.address, ethers.utils.parseEther("10")); + // approve DAI to aavePool + await token.connect(wallet0).approve(aaveAddress, parseEther("10")); + + //deposit DAI in aave + await aave.connect(wallet0).supply(DAI, parseEther("10"), wallet.address, 3228); + console.log("\tSupplied DAI on aave"); + + //borrow USDC from aave + await aave.connect(wallet0).borrow(USDC, parseUnits("1", 6), 1, 3228, wallet.address); + console.log("\tBorrowed USDC from aave"); + }); + + it("Should check position of user", async () => { + expect(await aDai.connect(wallet0).balanceOf(wallet.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + + expect(await usdcToken.connect(wallet0).balanceOf(wallet.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e6).toString() + ); + }); + }); + }); + describe("Aave position import with collateral", async () => { + it("Should migrate Aave position", async () => { + const DOMAIN_SEPARATOR = await aDai.connect(wallet0).DOMAIN_SEPARATOR(); + const PERMIT_TYPEHASH = "0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9"; + + let nonce = (await aDai.connect(wallet0).nonces(wallet.address)).toNumber(); + //Approving max amount + const amount = ethers.constants.MaxUint256; + const expiry = Date.now() + 20 * 60; + + const digest = keccak256( + ethers.utils.solidityPack( + ["bytes1", "bytes1", "bytes32", "bytes32"], + [ + "0x19", + "0x01", + DOMAIN_SEPARATOR, + keccak256( + defaultAbiCoder.encode( + ["bytes32", "address", "address", "uint256", "uint256", "uint256"], + [PERMIT_TYPEHASH, wallet.address, dsaWallet0.address, amount, nonce, expiry] + ) + ) + ] + ) + ); + const { v, r, s } = ecsign(Buffer.from(digest.slice(2), "hex"), Buffer.from(wallet.privateKey.slice(2), "hex")); + + const amount0 = new BigNumber( + new BigNumber((await aave.connect(wallet0).getUserAccountData(wallet.address)).totalDebtBase) + ); + let params = { + tokens: [USDC], + amounts: [amount0.toFixed(0)] + }; + const flashData = ( + await axios.get("https://api.instadapp.io/defi/optimism/flashloan/v2", { + params: params + }) + ).data; + + const fees = flashData.bestFee; + const amountB = new BigNumber(amount0.toString()).multipliedBy(fees).dividedBy(1e4); + const amountWithFee = amount0.plus(amountB); + const data = flashData.bestData[0]; + + const flashSpells = [ + { + connector: "AAVE-V3-IMPORT-PERMIT-X", + method: "importAaveWithCollateral", + args: [ + wallet.address, + [[DAI], [USDC], false, [amountB.toFixed(0)]], + [[v], [ethers.utils.hexlify(r)], [ethers.utils.hexlify(s)], [expiry]], + [true] + ] + }, + { + connector: "INSTAPOOL-C", + method: "flashPayback", + args: [USDC, amountWithFee.toFixed(0), 0, 0] + } + ]; + + const spells = [ + { + connector: "INSTAPOOL-C", + method: "flashBorrowAndCast", + args: [USDC, amount0.toFixed(0), flashData.bestRoutes[0], encodeFlashcastData(flashSpells), data.toString()] + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet.address); + const receipt = await tx.wait(); + }); + + it("Should check DSA AAVE position", async () => { + expect(await aDai.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); }); });