// // running `npx hardhat test` automatically makes use of hardhat-waffle plugin // // => only dependency we need is "chai" // const { expect } = require("chai"); // const hre = require("hardhat"); // const { deployments, ethers } = hre; // const GelatoCoreLib = require("@gelatonetwork/core"); // //const { sleep } = GelatoCoreLib; // // Constants // const DAI_100 = ethers.utils.parseUnits("100", 18); // // Contracts // const InstaIndex = require("../../../pre-compiles/InstaIndex.json"); // const InstaList = require("../../../pre-compiles/InstaList.json"); // const InstaAccount = require("../../../pre-compiles/InstaAccount.json"); // const ConnectAuth = require("../../../pre-compiles/ConnectAuth.json"); // const ConnectGelato_ABI = require("../../../artifacts/contracts/contracts/connectors/ConnectGelato.sol/ConnectGelato.json") // .abi; // const ConnectMaker = require("../../../pre-compiles/ConnectMaker.json"); // const ConnectCompound = require("../../../pre-compiles/ConnectCompound.json"); // const IERC20 = require("../../../pre-compiles/IERC20.json"); // const IUniswapExchange = require("../../../pre-compiles/IUniswapExchange.json"); // describe("Move DAI lending from DSR to Compound", function () { // this.timeout(0); // if (hre.network.name !== "hardhat") { // console.error("Test Suite is meant to be run on hardhat only"); // process.exit(1); // } // // Wallet to use for local testing // let userWallet; // let userAddress; // let dsaAddress; // // Deployed instances // let connectMaker; // let connectCompound; // let gelatoCore; // let dai; // // Contracts to deploy and use for local testing // let dsa; // let mockDSR; // let mockCDAI; // let conditionCompareUints; // before(async function () { // // Reset back to a fresh forked state during runtime // await deployments.fixture(); // // Get Test Wallet for local testnet // [userWallet] = await ethers.getSigners(); // userAddress = await userWallet.getAddress(); // // Hardhat default accounts prefilled with 100 ETH // expect(await userWallet.getBalance()).to.be.gt( // ethers.utils.parseEther("10") // ); // // ===== DSA SETUP ================== // const instaIndex = await ethers.getContractAt( // InstaIndex.abi, // hre.network.config.InstaIndex // ); // const instaList = await ethers.getContractAt( // InstaList.abi, // hre.network.config.InstaList // ); // connectMaker = await ethers.getContractAt( // ConnectMaker.abi, // hre.network.config.ConnectMaker // ); // connectCompound = await ethers.getContractAt( // ConnectCompound.abi, // hre.network.config.ConnectCompound // ); // // Deploy DSA and get and verify ID of newly deployed DSA // const dsaIDPrevious = await instaList.accounts(); // await expect(instaIndex.build(userAddress, 1, userAddress)).to.emit( // instaIndex, // "LogAccountCreated" // ); // const dsaID = dsaIDPrevious.add(1); // await expect(await instaList.accounts()).to.be.equal(dsaID); // // Instantiate the DSA // dsaAddress = await instaList.accountAddr(dsaID); // dsa = await ethers.getContractAt(InstaAccount.abi, dsaAddress); // // ===== GELATO SETUP ================== // gelatoCore = await ethers.getContractAt( // GelatoCoreLib.GelatoCore.abi, // hre.network.config.GelatoCore // ); // // Add GelatoCore as auth on DSA // const addAuthData = await hre.run("abi-encode-withselector", { // abi: ConnectAuth.abi, // functionname: "add", // inputs: [gelatoCore.address], // }); // await dsa.cast( // [hre.network.config.ConnectAuth], // [addAuthData], // userAddress // ); // expect(await dsa.isAuth(gelatoCore.address)).to.be.true; // // Deployed Mocks for Testing // mockCDAI = await ethers.getContract("MockCDAI"); // mockDSR = await ethers.getContract("MockDSR"); // // Deployed Gelato Conditions for Testing // conditionCompareUints = await ethers.getContract( // "ConditionCompareUintsFromTwoSources" // ); // // ===== Dapp Dependencies SETUP ================== // // This test assumes our user has 100 DAI deposited in Maker DSR // dai = await ethers.getContractAt(IERC20.abi, hre.network.config.DAI); // expect(await dai.balanceOf(userAddress)).to.be.equal(0); // // Let's get the test user 100 DAI++ from Kyber // const daiUniswapExchange = await ethers.getContractAt( // IUniswapExchange.abi, // hre.network.config.DAI_UNISWAP // ); // await daiUniswapExchange.ethToTokenTransferInput( // 1, // 2525644800, // random timestamp in the future (year 2050) // userAddress, // { // value: ethers.utils.parseEther("2"), // } // ); // expect(await dai.balanceOf(userAddress)).to.be.gte(DAI_100); // // Next we transfer the 100 DAI into our DSA // await dai.transfer(dsa.address, DAI_100); // expect(await dai.balanceOf(dsa.address)).to.be.eq(DAI_100); // // Next we deposit the 100 DAI into the DSR // const depositDai = await hre.run("abi-encode-withselector", { // abi: ConnectMaker.abi, // functionname: "depositDai", // inputs: [DAI_100, 0, 0], // }); // await expect( // dsa.cast([hre.network.config.ConnectMaker], [depositDai], userAddress) // ) // .to.emit(dsa, "LogCast") // .withArgs(userAddress, userAddress, 0); // expect(await dai.balanceOf(dsa.address)).to.be.eq(0); // }); // it("#1: Gelato refinances DAI from DSR=>Compound, if better rate", async function () { // // ======= Condition setup ====== // // We instantiate the Rebalance Condition: // // Compound APY needs to be 10000000 per second points higher than DSR // const MIN_SPREAD = "10000000"; // const rebalanceCondition = new GelatoCoreLib.Condition({ // inst: conditionCompareUints.address, // data: await conditionCompareUints.getConditionData( // mockCDAI.address, // We are in DSR so we compare against CDAI => SourceA=CDAI // mockDSR.address, // SourceB=DSR // await hre.run("abi-encode-withselector", { // abi: (await hre.artifacts.readArtifact("MockCDAI")).abi, // functionname: "supplyRatePerSecond", // }), // CDAI data feed first (sourceAData) // await hre.run("abi-encode-withselector", { // abi: (await hre.artifacts.readArtifact("MockDSR")).abi, // functionname: "dsr", // }), // DSR data feed second (sourceBData) // MIN_SPREAD // ), // }); // // ======= Action/Spells setup ====== // // To assimilate to DSA SDK // const spells = []; // // We instantiate target1: Withdraw DAI from DSR and setId 1 for // // target2 Compound deposit to fetch DAI amount. // const connectorWithdrawFromDSR = new GelatoCoreLib.Action({ // addr: connectMaker.address, // data: await hre.run("abi-encode-withselector", { // abi: ConnectMaker.abi, // functionname: "withdrawDai", // inputs: [ethers.constants.MaxUint256, 0, 1], // }), // operation: GelatoCoreLib.Operation.Delegatecall, // }); // spells.push(connectorWithdrawFromDSR); // // We instantiate target2: Deposit DAI to CDAI and getId 1 // const connectorDepositCompound = new GelatoCoreLib.Action({ // addr: connectCompound.address, // data: await hre.run("abi-encode-withselector", { // abi: ConnectCompound.abi, // functionname: "deposit", // inputs: [dai.address, 0, 1, 0], // }), // operation: GelatoCoreLib.Operation.Delegatecall, // }); // spells.push(connectorDepositCompound); // // ======= Gelato Task Setup ========= // // A Gelato Task just combines Conditions with Actions // // You also specify how much GAS a Task consumes at max and the ceiling // // gas price under which you are willing to auto-transact. There is only // // one gas price in the current Gelato system: fast gwei read from Chainlink. // const GAS_LIMIT = "4000000"; // const GAS_PRICE_CEIL = ethers.utils.parseUnits("1000", "gwei"); // const taskRebalanceDSRToCDAIifBetter = new GelatoCoreLib.Task({ // conditions: [rebalanceCondition], // actions: spells, // selfProviderGasLimit: GAS_LIMIT, // selfProviderGasPriceCeil: GAS_PRICE_CEIL, // }); // // ======= Gelato Provider setup ====== // // Someone needs to pay for gas for automatic Task execution on Gelato. // // Gelato has the concept of a "Provider" to denote who is providing (depositing) // // ETH on Gelato in order to pay for automation gas. In our case, the User // // is paying for his own automation gas. Therefore, the User is a "Self-Provider". // // But since Gelato only talks to smart contract accounts, the User's DSA proxy // // plays the part of the "Self-Provider" on behalf of the User behind the DSA. // // A GelatoProvider is an object with the address of the provider - in our case // // the DSA address - and the address of the "ProviderModule". This module // // fulfills certain functions like encoding the execution payload for the Gelato // // protocol. Check out ./contracts/ProviderModuleDSA.sol to see what it does. // const gelatoSelfProvider = new GelatoCoreLib.GelatoProvider({ // addr: dsa.address, // module: hre.network.config.ProviderModuleDSA, // }); // // ======= Executor Setup ========= // // For local Testing purposes our test User account will play the role of the Gelato // // Executor network because this logic is non-trivial to fork into a local instance // await gelatoCore.stakeExecutor({ // value: await gelatoCore.minExecutorStake(), // }); // expect(await gelatoCore.isExecutorMinStaked(userAddress)).to.be.true; // // ======= Gelato Task Provision ========= // // Gelato requires some initial setup via its multiProvide API // // We must 1) provide ETH to pay for future automation gas, 2) we must // // assign an Executor network to the Task, 3) we must tell Gelato what // // "ProviderModule" we want to use for our Task. // // Since our DSA proxy is the one through which we interact with Gelato, // // we must do this setup via the DSA proxy by using ConnectGelato // const TASK_AUTOMATION_FUNDS = await gelatoCore.minExecProviderFunds( // GAS_LIMIT, // GAS_PRICE_CEIL // ); // await dsa.cast( // [hre.network.config.ConnectGelato], // targets // [ // await hre.run("abi-encode-withselector", { // abi: ConnectGelato_ABI, // functionname: "multiProvide", // inputs: [ // userAddress, // [], // [hre.network.config.ProviderModuleDSA], // TASK_AUTOMATION_FUNDS, // 0, // _getId // 0, // _setId // ], // }), // ], // datas // userAddress, // origin // { // value: TASK_AUTOMATION_FUNDS, // gasLimit: 5000000, // } // ); // expect(await gelatoCore.providerFunds(dsa.address)).to.be.gte( // TASK_AUTOMATION_FUNDS // ); // expect( // await gelatoCore.isProviderLiquid(dsa.address, GAS_LIMIT, GAS_PRICE_CEIL) // ); // expect(await gelatoCore.executorByProvider(dsa.address)).to.be.equal( // userAddress // ); // expect( // await gelatoCore.isModuleProvided( // dsa.address, // hre.network.config.ProviderModuleDSA // ) // ).to.be.true; // // ======= 📣 TASK SUBMISSION 📣 ========= // // In Gelato world our DSA is the User. So we must submit the Task // // to Gelato via our DSA and hence use ConnectGelato again. // const expiryDate = 0; // await expect( // dsa.cast( // [hre.network.config.ConnectGelato], // targets // [ // await hre.run("abi-encode-withselector", { // abi: ConnectGelato_ABI, // functionname: "submitTask", // inputs: [ // gelatoSelfProvider, // taskRebalanceDSRToCDAIifBetter, // expiryDate, // ], // }), // ], // datas // userAddress, // origin // { // gasLimit: 5000000, // } // ) // ).to.emit(gelatoCore, "LogTaskSubmitted"); // // Task Receipt: a successfully submitted Task in Gelato // // is wrapped in a TaskReceipt. For testing we instantiate the TaskReceipt // // for our to be submitted Task. // const taskReceiptId = await gelatoCore.currentTaskReceiptId(); // const taskReceipt = new GelatoCoreLib.TaskReceipt({ // id: taskReceiptId, // userProxy: dsa.address, // provider: gelatoSelfProvider, // tasks: [taskRebalanceDSRToCDAIifBetter], // expiryDate, // }); // // ======= 📣 TASK EXECUTION 📣 ========= // // This stuff is normally automated by the Gelato Network and Dapp Developers // // and their Users don't have to take care of it. However, for local testing // // we simulate the Gelato Execution logic. // // First we fetch the gelatoGasPrice as fed by ChainLink oracle. Gelato // // allows Users to specify a maximum fast gwei gas price for their Tasks // // to remain executable up until. // const gelatoGasPrice = await hre.run("fetchGelatoGasPrice"); // expect(gelatoGasPrice).to.be.lte( // taskRebalanceDSRToCDAIifBetter.selfProviderGasPriceCeil // ); // // Let's first check if our Task is executable. Since both MockDSR and MockCDAI // // are deployed with a normalized per second rate of APY_2_PERCENT_IN_SECONDS // // (1000000000627937192491029810 in 10**27 precision) in both of them, we // // expect ConditionNotOk because ANotGreaterOrEqualToBbyMinspread. // // Check out contracts/ConditionCompareUintsFromTwoSources.sol to see how // // how the comparison of MockDSR and MockCDAI is implemented in Condition code. // expect( // await gelatoCore.canExec( // taskReceipt, // taskRebalanceDSRToCDAIifBetter.selfProviderGasLimit, // gelatoGasPrice // ) // ).to.be.equal("ConditionNotOk:ANotGreaterOrEqualToBbyMinspread"); // // We defined a MIN_SPREAD of 10000000 points in the per second rate // // for our ConditionCompareUintsFromTwoSources. So we now // // set the CDAI.supplyRatePerSecond to be 10000000 higher than MockDSR.dsr // // and expect it to mean that our Task becomes executable. // await mockCDAI.setSupplyRatePerSecond( // (await mockDSR.dsr()).add(MIN_SPREAD) // ); // expect( // await gelatoCore.canExec( // taskReceipt, // taskRebalanceDSRToCDAIifBetter.selfProviderGasLimit, // gelatoGasPrice // ) // ).to.be.equal("OK"); // // To verify whether the execution of DSR=>CDAI has been successful in this Testing // // we look at changes in the CDAI balance of the DSA // const cDAI = await ethers.getContractAt( // IERC20.abi, // hre.network.config.CDAI // ); // const dsaCDAIBefore = await cDAI.balanceOf(dsa.address); // // For testing we now simulate automatic Task Execution ❗ // await expect( // gelatoCore.exec(taskReceipt, { // gasPrice: gelatoGasPrice, // Exectutor must use gelatoGasPrice (Chainlink fast gwei) // gasLimit: taskRebalanceDSRToCDAIifBetter.selfProviderGasLimit, // }) // ).to.emit(gelatoCore, "LogExecSuccess"); // // Since the Execution was successful, we now expect our DSA to hold more // // CDAI then before. This concludes our testing. // expect(await cDAI.balanceOf(dsa.address)).to.be.gt(dsaCDAIBefore); // }); // });