diff --git a/scripts/tests/buildDSAv2.ts b/scripts/tests/buildDSAv2.ts index 163db741..e54dd68d 100644 --- a/scripts/tests/buildDSAv2.ts +++ b/scripts/tests/buildDSAv2.ts @@ -3,6 +3,7 @@ import { ethers } from "hardhat"; import { addresses as addressesPolygon } from "./polygon/addresses"; import { addresses as addressesArbitrum } from "./arbitrum/addresses"; import { addresses as addressesAvalanche } from "./avalanche/addresses"; +import { addresses as addressesOptimism } from "./optimism/addresses"; import { addresses } from "./mainnet/addresses"; import { abis } from "../constant/abis"; import { abi } from "../../deployements/mainnet/Implementation_m1.sol/InstaImplementationM1.json"; @@ -11,6 +12,7 @@ function getAddress(network: string | undefined) { if (network === "polygon") return addressesPolygon.core.instaIndex; else if (network === "arbitrum") return addressesArbitrum.core.instaIndex; else if (network === "avalanche") return addressesAvalanche.core.instaIndex; + else if (network === "optimism") return addressesOptimism.core.instaIndex; else return addresses.core.instaIndex; } diff --git a/scripts/tests/deployAndEnableConnector.ts b/scripts/tests/deployAndEnableConnector.ts index e08142e4..fe3d71dd 100644 --- a/scripts/tests/deployAndEnableConnector.ts +++ b/scripts/tests/deployAndEnableConnector.ts @@ -3,6 +3,7 @@ import { addresses } from "./mainnet/addresses"; import { abis } from "../constant/abis"; import { addresses as addressesArbitrum } from "./arbitrum/addresses"; import { addresses as addressesAvalanche } from "./avalanche/addresses"; +import { addresses as addressesOptimism } from "./optimism/addresses"; import hre from "hardhat"; import type { Signer, Contract } from "ethers"; @@ -22,6 +23,7 @@ function getAddress(network: string | undefined) { if (network === "polygon") return addressesPolygon; else if (network === "arbitrum") return addressesArbitrum; else if (network === "avalanche") return addressesAvalanche; + else if (network === "optimism") return addressesOptimism; else return addresses; } diff --git a/scripts/tests/getMasterSigner.ts b/scripts/tests/getMasterSigner.ts index c09fec74..040e4e4f 100644 --- a/scripts/tests/getMasterSigner.ts +++ b/scripts/tests/getMasterSigner.ts @@ -3,12 +3,14 @@ import { addresses } from "./mainnet/addresses"; import { addresses as addressesPolygon } from "./polygon/addresses"; import { addresses as addressesArbitrum } from "./arbitrum/addresses"; import { addresses as addressesAvalanche } from "./avalanche/addresses"; +import { addresses as addressesOptimism } from "./optimism/addresses"; import { abis } from "../constant/abis"; function getAddress(network: string | undefined) { if (network === "polygon") return addressesPolygon.core.instaIndex; else if (network === "arbitrum") return addressesArbitrum.core.instaIndex; else if (network === "avalanche") return addressesAvalanche.core.instaIndex; + else if (network === "optimism") return addressesOptimism.core.instaIndex; else return addresses.core.instaIndex; } diff --git a/scripts/tests/optimism/addresses.ts b/scripts/tests/optimism/addresses.ts new file mode 100644 index 00000000..c41acccc --- /dev/null +++ b/scripts/tests/optimism/addresses.ts @@ -0,0 +1,10 @@ +export const addresses: Record = { + connectors: { + basic: "", + auth: "", + }, + core: { + connectorsV2: "0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14", + instaIndex: "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25", + }, +}; \ No newline at end of file diff --git a/scripts/tests/optimism/tokens.ts b/scripts/tests/optimism/tokens.ts new file mode 100644 index 00000000..83b66b68 --- /dev/null +++ b/scripts/tests/optimism/tokens.ts @@ -0,0 +1,94 @@ +import { Provider } from "@ethersproject/abstract-provider"; +import { Signer } from "@ethersproject/abstract-signer"; +import { ethers } from "hardhat"; + +const mineTx = async (tx: any) => { + await (await tx).wait(); +}; + +export const tokens = { + matic: { + type: "token", + symbol: "MATIC", + name: "Matic", + address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + decimals: 18, + }, + eth: { + type: "token", + symbol: "ETH", + name: "Ethereum", + address: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", + decimals: 18, + }, + dai: { + type: "token", + symbol: "DAI", + name: "DAI Stable", + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + decimals: 18, + }, + usdc: { + type: "token", + symbol: "USDC", + name: "USD Coin", + address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + decimals: 6, + }, +}; + +export const tokenMapping: Record = { + usdc: { + impersonateSigner: "0x6e7a5fafcec6bb1e78bae2a1f0b612012bf14827", + address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + abi: [ + "function mint(address _to, uint256 _amount) external returns (bool);", + ], + process: async function (owner: Signer | Provider, to: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + + await mineTx(contract.mint(to, amt)); + }, + }, + dai: { + impersonateSigner: "0x4a35582a710e1f4b2030a3f826da20bfb6703c09", + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + abi: ["function transfer(address to, uint value)"], + process: async function (owner: Signer | Provider, to: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + await mineTx(contract.transfer(to, amt)); + }, + }, + usdt: { + impersonateSigner: "0x0d0707963952f2fba59dd06f2b425ace40b492fe", + address: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + abi: [ + "function issue(uint amount)", + "function transfer(address to, uint value)", + ], + process: async function (owner: Signer | Provider, address: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + + await mineTx(contract.issue(amt)); + await mineTx(contract.transfer(address, amt)); + }, + }, + wbtc: { + impersonateSigner: "0xdc9232e2df177d7a12fdff6ecbab114e2231198d", + address: "0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6", + abi: ["function mint(address _to, uint256 _amount) public returns (bool)"], + process: async function (owner: Signer | Provider, address: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + await mineTx(contract.mint(address, amt)); + }, + }, + inst: { + impersonateSigner: "0xf1f22f25f748f79263d44735198e023b72806ab1", + address: "0xf50d05a1402d0adafa880d36050736f9f6ee7dee", + abi: ["function transfer(address to, uint value)"], + process: async function (owner: Signer | Provider, address: any, amt: any) { + const contract = new ethers.Contract(this.address, this.abi, owner); + await mineTx(contract.transfer(address, amt)); + }, + }, +}; diff --git a/scripts/tests/run-tests.ts b/scripts/tests/run-tests.ts index 9a3c3927..5b2f22bd 100644 --- a/scripts/tests/run-tests.ts +++ b/scripts/tests/run-tests.ts @@ -12,7 +12,7 @@ async function testRunner() { name: "chain", message: "What chain do you want to run tests on?", type: "list", - choices: ["mainnet", "polygon", "avalanche", "arbitrum"], + choices: ["mainnet", "polygon", "avalanche", "arbitrum", "optimism"], }, ]); const testsPath = join(__dirname, "../../test", chain); diff --git a/test/avalanche/aave/aaveV3-import-test.ts b/test/avalanche/aave/aaveV3-import-test.ts index cb2cd71c..563ccec0 100644 --- a/test/avalanche/aave/aaveV3-import-test.ts +++ b/test/avalanche/aave/aaveV3-import-test.ts @@ -239,7 +239,7 @@ describe("Import Aave v3 Position for Avalanche", function () { }); describe("check user AAVE position", async () => { - it("Should create Aave v3 position of DAI(collateral) and USDC(debt)", async () => { + it("Should create Aave v3 position of DAI(collateral) and ETH(debt)", async () => { // approve DAI to aavePool await token.connect(wallet0).approve(aaveAddress, parseEther("8")); @@ -247,9 +247,9 @@ describe("Import Aave v3 Position for Avalanche", function () { await aave.connect(wallet0).supply(DAI, parseEther("8"), wallet.address, 3228); console.log("Supplied DAI on aave"); - //borrow USDC from aave + //borrow ETH from aave await aave.connect(wallet0).borrow(ETH, parseUnits("3", 6), 2, 3228, wallet.address); - console.log("Borrowed USDC from aave"); + console.log("Borrowed ETH from aave"); }); it("Should check position of user", async () => { @@ -350,4 +350,4 @@ describe("Import Aave v3 Position for Avalanche", function () { ); }); }); -}); +}); \ No newline at end of file diff --git a/test/optimism/aave/aaveV3-import-test.ts b/test/optimism/aave/aaveV3-import-test.ts new file mode 100644 index 00000000..1305f4ca --- /dev/null +++ b/test/optimism/aave/aaveV3-import-test.ts @@ -0,0 +1,314 @@ +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 { Hex } from "web3/utils"; + +const aDaiAddress = "0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE"; +const aaveAddress = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"; +let account = "0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09"; +const DAI = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"; +const USDC = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"; +const flashAddress = "0x810D6b2425Dc5523525D1F45CC548ae9a085F5Ea"; +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; + +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 flashAbi = [ + { + inputs: [ + { internalType: "address[]", name: "_tokens", type: "address[]" }, + { internalType: "uint256[]", name: "_amounts", type: "uint256[]" } + ], + name: "getBestRoutes", + outputs: [ + { internalType: "uint16[]", name: "", type: "uint16[]" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "bytes[]", name: "", type: "bytes[]" } + ], + stateMutability: "view", + type: "function" + } +]; + +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" + } +]; + +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); +const flashLoan = new ethers.Contract(flashAddress, flashAbi); + +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("8")); + + 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("8")); + + //deposit DAI in aave + await aave.connect(wallet0).supply(DAI, parseEther("8"), wallet.address, 3228); + console.log("Supplied DAI on aave"); + + //borrow USDC from aave + await aave.connect(wallet0).borrow(USDC, parseUnits("1", 6), 2, 3228, wallet.address); + console.log("Borrowed USDC from aave"); + }); + + it("Should check position of user", async () => { + expect(await aDai.connect(wallet0).balanceOf(wallet.address)).to.be.gte( + new BigNumber(8).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("8") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("8")); + }); + }); + + 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(await usdcToken.connect(wallet0).balanceOf(wallet.address)); + let flashData = await flashLoan.connect(wallet0).getBestRoutes([USDC], [amount0.toFixed(0)]); + const fees = flashData[1].toNumber(); + const amountB = new BigNumber(amount0.toString()).multipliedBy(fees).dividedBy(1e4); + const amountWithFee = amount0.plus(amountB); + const data = flashData[2][0]; + console.log(data.toString()); + console.log(amountWithFee.toFixed(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.toString(), 8, 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(8).multipliedBy(1e18).toString() + ); + }); + }); +});