diff --git a/contracts/optimism/connectors/aave/v3-import-permit/events.sol b/contracts/optimism/connectors/aave/v3-import-permit/events.sol new file mode 100644 index 00000000..10a93d70 --- /dev/null +++ b/contracts/optimism/connectors/aave/v3-import-permit/events.sol @@ -0,0 +1,15 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +contract Events { + event LogAaveV3ImportWithPermit( + address indexed user, + address[] atokens, + string[] supplyIds, + string[] borrowIds, + uint256[] flashLoanFees, + uint256[] supplyAmts, + uint256[] borrowAmts + ); +} diff --git a/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol b/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol new file mode 100644 index 00000000..61b1687d --- /dev/null +++ b/contracts/optimism/connectors/aave/v3-import-permit/helpers.sol @@ -0,0 +1,316 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import { TokenInterface, AccountInterface } from "../../../common/interfaces.sol"; +import { AaveInterface, AavePoolProviderInterface, AaveDataProviderInterface } from "./interface.sol"; +import "./events.sol"; +import "./interface.sol"; + +abstract contract Helper is DSMath, Basic { + /** + * @dev Aave referal code + */ + uint16 internal constant referalCode = 3228; + + /** + * @dev Aave Lending Pool Provider + */ + AavePoolProviderInterface internal constant aaveProvider = + AavePoolProviderInterface(0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb); + + /** + * @dev Aave Protocol Data Provider + */ + AaveDataProviderInterface internal constant aaveData = + AaveDataProviderInterface(0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654); + + function getIsColl(address token, address user) + internal + view + returns (bool isCol) + { + (, , , , , , , , isCol) = aaveData.getUserReserveData(token, user); + } + + struct ImportData { + address[] _supplyTokens; + address[] _borrowTokens; + ATokenInterface[] aTokens; + uint256[] supplyAmts; + uint256[] variableBorrowAmts; + uint256[] variableBorrowAmtsWithFee; + uint256[] stableBorrowAmts; + uint256[] stableBorrowAmtsWithFee; + uint256[] totalBorrowAmts; + uint256[] totalBorrowAmtsWithFee; + bool convertStable; + } + + struct ImportInputData { + address[] supplyTokens; + address[] borrowTokens; + bool convertStable; + uint256[] flashLoanFees; + } + + struct SignedPermits { + uint8[] v; + bytes32[] r; + bytes32[] s; + uint256[] expiry; + } +} + +contract AaveHelpers is Helper { + function getBorrowAmount(address _token, address userAccount) + internal + view + returns (uint256 stableBorrow, uint256 variableBorrow) + { + ( + , + address stableDebtTokenAddress, + address variableDebtTokenAddress + ) = aaveData.getReserveTokensAddresses(_token); + + stableBorrow = ATokenInterface(stableDebtTokenAddress).balanceOf( + userAccount + ); + variableBorrow = ATokenInterface(variableDebtTokenAddress).balanceOf( + userAccount + ); + } + + function getBorrowAmounts( + address userAccount, + AaveInterface aave, + ImportInputData memory inputData, + ImportData memory data + ) internal returns (ImportData memory) { + if (inputData.borrowTokens.length > 0) { + data._borrowTokens = new address[](inputData.borrowTokens.length); + data.variableBorrowAmts = new uint256[]( + inputData.borrowTokens.length + ); + data.variableBorrowAmtsWithFee = new uint256[]( + inputData.borrowTokens.length + ); + data.stableBorrowAmts = new uint256[]( + inputData.borrowTokens.length + ); + data.stableBorrowAmtsWithFee = new uint256[]( + inputData.borrowTokens.length + ); + data.totalBorrowAmts = new uint256[](inputData.borrowTokens.length); + data.totalBorrowAmtsWithFee = new uint256[]( + inputData.borrowTokens.length + ); + for (uint256 i = 0; i < inputData.borrowTokens.length; i++) { + for (uint256 j = i; j < inputData.borrowTokens.length; j++) { + if (j != i) { + require( + inputData.borrowTokens[i] != + inputData.borrowTokens[j], + "token-repeated" + ); + } + } + } + for (uint256 i = 0; i < inputData.borrowTokens.length; i++) { + address _token = inputData.borrowTokens[i] == ethAddr + ? wethAddr + : inputData.borrowTokens[i]; + data._borrowTokens[i] = _token; + + ( + data.stableBorrowAmts[i], + data.variableBorrowAmts[i] + ) = getBorrowAmount(_token, userAccount); + + if (data.variableBorrowAmts[i] != 0) { + data.variableBorrowAmtsWithFee[i] = add( + data.variableBorrowAmts[i], + inputData.flashLoanFees[i] + ); + data.stableBorrowAmtsWithFee[i] = data.stableBorrowAmts[i]; + } else { + data.stableBorrowAmtsWithFee[i] = add( + data.stableBorrowAmts[i], + inputData.flashLoanFees[i] + ); + } + + data.totalBorrowAmts[i] = add( + data.stableBorrowAmts[i], + data.variableBorrowAmts[i] + ); + data.totalBorrowAmtsWithFee[i] = add( + data.stableBorrowAmtsWithFee[i], + data.variableBorrowAmtsWithFee[i] + ); + + if (data.totalBorrowAmts[i] > 0) { + uint256 _amt = data.totalBorrowAmts[i]; + TokenInterface(_token).approve(address(aave), _amt); + } + } + } + return data; + } + + function getSupplyAmounts( + address userAccount, + ImportInputData memory inputData, + ImportData memory data + ) internal view returns (ImportData memory) { + data.supplyAmts = new uint256[](inputData.supplyTokens.length); + data._supplyTokens = new address[](inputData.supplyTokens.length); + data.aTokens = new ATokenInterface[](inputData.supplyTokens.length); + + for (uint256 i = 0; i < inputData.supplyTokens.length; i++) { + for (uint256 j = i; j < inputData.supplyTokens.length; j++) { + if (j != i) { + require( + inputData.supplyTokens[i] != inputData.supplyTokens[j], + "token-repeated" + ); + } + } + } + for (uint256 i = 0; i < inputData.supplyTokens.length; i++) { + address _token = inputData.supplyTokens[i] == ethAddr + ? wethAddr + : inputData.supplyTokens[i]; + (address _aToken, , ) = aaveData.getReserveTokensAddresses(_token); + data._supplyTokens[i] = _token; + data.aTokens[i] = ATokenInterface(_aToken); + data.supplyAmts[i] = data.aTokens[i].balanceOf(userAccount); + } + + return data; + } + + function _paybackBehalfOne( + AaveInterface aave, + address token, + uint256 amt, + uint256 rateMode, + address user + ) private { + aave.repay(token, amt, rateMode, user); + } + + function _PaybackStable( + uint256 _length, + AaveInterface aave, + address[] memory tokens, + uint256[] memory amts, + address user + ) internal { + for (uint256 i = 0; i < _length; i++) { + if (amts[i] > 0) { + _paybackBehalfOne(aave, tokens[i], amts[i], 1, user); + } + } + } + + function _PaybackVariable( + uint256 _length, + AaveInterface aave, + address[] memory tokens, + uint256[] memory amts, + address user + ) internal { + for (uint256 i = 0; i < _length; i++) { + if (amts[i] > 0) { + _paybackBehalfOne(aave, tokens[i], amts[i], 2, user); + } + } + } + + function _PermitATokens( + address userAccount, + ATokenInterface[] memory aTokenContracts, + address[] memory tokens, + uint8[] memory v, + bytes32[] memory r, + bytes32[] memory s, + uint256[] memory expiry + ) internal { + for (uint256 i = 0; i < tokens.length; i++) { + aTokenContracts[i].permit( + userAccount, + address(this), + uint256(-1), + expiry[i], + v[i], + r[i], + s[i] + ); + } + } + + function _TransferAtokens( + uint256 _length, + AaveInterface aave, + ATokenInterface[] memory atokenContracts, + uint256[] memory amts, + address[] memory tokens, + 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], true); + } + } + } + } + + function _BorrowVariable( + uint256 _length, + AaveInterface aave, + address[] memory tokens, + uint256[] memory amts + ) internal { + for (uint256 i = 0; i < _length; i++) { + if (amts[i] > 0) { + _borrowOne(aave, tokens[i], amts[i], 2); + } + } + } + + function _BorrowStable( + uint256 _length, + AaveInterface aave, + address[] memory tokens, + uint256[] memory amts + ) internal { + for (uint256 i = 0; i < _length; i++) { + if (amts[i] > 0) { + _borrowOne(aave, tokens[i], amts[i], 1); + } + } + } + + function _borrowOne( + AaveInterface aave, + address token, + uint256 amt, + uint256 rateMode + ) private { + aave.borrow(token, amt, rateMode, referalCode, address(this)); + } +} diff --git a/contracts/optimism/connectors/aave/v3-import-permit/interface.sol b/contracts/optimism/connectors/aave/v3-import-permit/interface.sol new file mode 100644 index 00000000..f7ceed9e --- /dev/null +++ b/contracts/optimism/connectors/aave/v3-import-permit/interface.sol @@ -0,0 +1,117 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +interface AaveInterface { + function supply( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external; + + function withdraw( + address _asset, + uint256 _amount, + address _to + ) external; + + function borrow( + address _asset, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode, + address _onBehalfOf + ) external; + + function repay( + address _asset, + uint256 _amount, + uint256 _rateMode, + address _onBehalfOf + ) external; + + function setUserUseReserveAsCollateral( + address _asset, + bool _useAsCollateral + ) external; + + function swapBorrowRateMode(address _asset, uint256 _rateMode) external; + + function getUserAccountData(address user) + external + view + returns ( + uint256 totalCollateralBase, + uint256 totalDebtBase, + uint256 availableBorrowsBase, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ); +} + +interface ATokenInterface { + function scaledBalanceOf(address _user) external view returns (uint256); + + function isTransferAllowed(address _user, uint256 _amount) + external + view + returns (bool); + + function balanceOf(address _user) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); + + function allowance(address, address) external returns (uint256); +} + +interface AavePoolProviderInterface { + function getPool() external view returns (address); +} + +interface AaveDataProviderInterface { + function getReserveTokensAddresses(address _asset) + external + view + returns ( + address aTokenAddress, + address stableDebtTokenAddress, + address variableDebtTokenAddress + ); + + function getUserReserveData(address _asset, address _user) + external + view + returns ( + uint256 currentATokenBalance, + uint256 currentStableDebt, + uint256 currentVariableDebt, + uint256 principalStableDebt, + uint256 scaledVariableDebt, + uint256 stableBorrowRate, + uint256 liquidityRate, + uint40 stableRateLastUpdated, + bool usageAsCollateralEnabled + ); +} + +interface AaveAddressProviderRegistryInterface { + function getAddressesProvidersList() + external + view + returns (address[] memory); +} diff --git a/contracts/optimism/connectors/aave/v3-import-permit/main.sol b/contracts/optimism/connectors/aave/v3-import-permit/main.sol new file mode 100644 index 00000000..17fd9a6c --- /dev/null +++ b/contracts/optimism/connectors/aave/v3-import-permit/main.sol @@ -0,0 +1,133 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; +/** + * @title Aave v3 import connector. + * @dev Import EOA's aave V3 position to DSA's aave v3 position + */ + +import { TokenInterface, AccountInterface } from "../../../common/interfaces.sol"; +import { AaveInterface, ATokenInterface } from "./interface.sol"; +import "./helpers.sol"; +import "./events.sol"; + +contract AaveV3ImportPermitResolver is AaveHelpers { + function _importAave( + address userAccount, + ImportInputData memory inputData, + SignedPermits memory permitData + ) 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"); + + 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; + _TransferAtokens( + data._supplyTokens.length, + aave, + data.aTokens, + data.supplyAmts, + data._supplyTokens, + 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 = "LogAaveV3ImportWithPermit(address,bool,address[],address[],uint256[],uint256[],uint256[],uint256[])"; + _eventParam = abi.encode( + userAccount, + inputData.convertStable, + inputData.supplyTokens, + inputData.borrowTokens, + inputData.flashLoanFees, + data.supplyAmts, + data.stableBorrowAmts, + data.variableBorrowAmts + ); + } + + /** + * @dev Import aave V3 position . + * @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 + */ + function importAave( + address userAccount, + ImportInputData memory inputData, + SignedPermits memory permitData + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + (_eventName, _eventParam) = _importAave( + userAccount, + inputData, + permitData + ); + } +} + +contract ConnectV2AaveV3ImportPermitOptimism is AaveV3ImportPermitResolver { + string public constant name = "Aave-v3-import-permit-v1"; +} diff --git a/test/optimism/aave/aaveV3-import-test.ts b/test/optimism/aave/aaveV3-import-test.ts new file mode 100644 index 00000000..21b1a3ec --- /dev/null +++ b/test/optimism/aave/aaveV3-import-test.ts @@ -0,0 +1,357 @@ +import { expect, should } from "chai"; +import hre, { ethers, waffle } from "hardhat"; +import type { Signer, Contract } from "ethers"; +import { ecsign, ecrecover, pubToAddress } from "ethereumjs-util"; +import { keccak256 } from "@ethersproject/keccak256"; +import { defaultAbiCoder } from "@ethersproject/abi"; +import { BigNumber } from "bignumber.js"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { addresses } from "../../../scripts/tests/optimism/addresses"; +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { abis } from "../../../scripts/constant/abis"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { parseEther, parseUnits } from "ethers/lib/utils"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import encodeFlashcastData from "../../../scripts/tests/encodeFlashcastData"; +import { ConnectV2AaveV3ImportPermitOptimism__factory, IERC20__factory } from "../../../typechain"; +import axios from "axios"; + +const aDaiAddress = "0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE"; +const aaveAddress = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"; +let account = "0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09"; +const DAI = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"; +const USDC = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"; +const mnemonic = "test test test test test test test test test test test junk"; +const connectorName = "AAVE-V3-IMPORT-PERMIT-X"; +let signer: any, wallet0: any, wallet: any; + +const ABI = [ + "function DOMAIN_SEPARATOR() public view returns (bytes32)", + "function balanceOf(address account) public view returns (uint256)", + "function nonces(address owner) public view returns (uint256)" +]; + +const aaveAbi = [ + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "uint256", name: "interestRateMode", type: "uint256" }, + { internalType: "uint16", name: "referralCode", type: "uint16" }, + { internalType: "address", name: "onBehalfOf", type: "address" } + ], + name: "borrow", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "address", name: "onBehalfOf", type: "address" }, + { internalType: "uint16", name: "referralCode", type: "uint16" } + ], + name: "supply", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "user", + type: "address" + } + ], + name: "getUserAccountData", + outputs: [ + { + internalType: "uint256", + name: "totalCollateralBase", + type: "uint256" + }, + { + internalType: "uint256", + name: "totalDebtBase", + type: "uint256" + }, + { + internalType: "uint256", + name: "availableBorrowsBase", + type: "uint256" + }, + { + internalType: "uint256", + name: "currentLiquidationThreshold", + type: "uint256" + }, + { + internalType: "uint256", + name: "ltv", + type: "uint256" + }, + { + internalType: "uint256", + name: "healthFactor", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + } +]; + +const erc20Abi = [ + { + constant: false, + inputs: [ + { + name: "_spender", + type: "address" + }, + { + name: "_value", + type: "uint256" + } + ], + name: "approve", + outputs: [ + { + name: "", + type: "bool" + } + ], + payable: false, + stateMutability: "nonpayable", + type: "function" + }, + { + constant: true, + inputs: [ + { + name: "_owner", + type: "address" + } + ], + name: "balanceOf", + outputs: [ + { + name: "balance", + type: "uint256" + } + ], + payable: false, + stateMutability: "view", + type: "function" + }, + { + constant: false, + inputs: [ + { + name: "_to", + type: "address" + }, + { + name: "_value", + type: "uint256" + } + ], + name: "transfer", + outputs: [ + { + name: "", + type: "bool" + } + ], + payable: false, + stateMutability: "nonpayable", + type: "function" + } +]; + +const token = new ethers.Contract(DAI, erc20Abi); +const aDai = new ethers.Contract(aDaiAddress, ABI); +const usdcToken = new ethers.Contract(USDC, erc20Abi); +const aave = new ethers.Contract(aaveAddress, aaveAbi); + +describe("Import Aave v3 Position for Optimism", function () { + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + const wallet = ethers.Wallet.fromMnemonic(mnemonic); + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + //@ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 5405099 + } + } + ] + }); + masterSigner = await getMasterSigner(); + [wallet0] = await ethers.getSigners(); + await hre.network.provider.send("hardhat_setBalance", [account, ethers.utils.parseEther("10").toHexString()]); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [account] + }); + + signer = await ethers.getSigner(account); + + await token.connect(signer).transfer(wallet0.address, ethers.utils.parseEther("10")); + + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2AaveV3ImportPermitOptimism__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + }); + + describe("check user AAVE position", async () => { + it("Should create Aave v3 position of DAI(collateral), and USDC(debt)", async () => { + // 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("Deployment", async () => { + it("Should set correct name", async () => { + expect(await connector.name()).to.eq("Aave-v3-import-permit-v1"); + }); + }); + + describe("DSA wallet setup", async () => { + it("Should build DSA v2", async () => { + dsaWallet0 = await buildDSAv2(wallet.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("User's borrows", async () => { + it("Should check the totalDebtBase of user", async () => { + console.log( + `\ttotalDebtBase: ${(await aave.connect(wallet0).getUserAccountData(wallet.address)).totalDebtBase.toString()}` + ); + }); + }); + + describe("Aave position migration", 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: "importAave", + args: [ + wallet.address, + [[DAI], [USDC], false, [amountB.toFixed(0)]], + [[v], [ethers.utils.hexlify(r)], [ethers.utils.hexlify(s)], [expiry]] + ] + }, + { + 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() + ); + }); + }); +});