diff --git a/contracts/mainnet/connectors/compound-import/events.sol b/contracts/mainnet/connectors/compound-import/events.sol new file mode 100644 index 00000000..79fd7d69 --- /dev/null +++ b/contracts/mainnet/connectors/compound-import/events.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma experimental ABIEncoderV2; + +contract Events { + event LogCompoundImport( + address indexed user, + address[] ctokens, + string[] supplyIds, + string[] borrowIds, + uint256[] supplyAmts, + uint256[] borrowAmts + ); +} diff --git a/contracts/mainnet/connectors/compound-import/helpers.sol b/contracts/mainnet/connectors/compound-import/helpers.sol new file mode 100644 index 00000000..93acc1fb --- /dev/null +++ b/contracts/mainnet/connectors/compound-import/helpers.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; +import { TokenInterface, AccountInterface } from "../../common/interfaces.sol"; +import { ComptrollerInterface, CompoundMappingInterface, CETHInterface, CTokenInterface } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev Compound CEth + */ + CETHInterface internal constant cEth = + CETHInterface(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5); + + /** + * @dev Compound Comptroller + */ + ComptrollerInterface internal constant troller = + ComptrollerInterface(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); + + /** + * @dev Compound Mapping + */ + CompoundMappingInterface internal constant compMapping = + CompoundMappingInterface(0xe7a85d0adDB972A4f0A4e57B698B37f171519e88); + + struct ImportData { + address[] cTokens; // is the list of all tokens the user has interacted with (supply/borrow) -> used to enter markets + uint256[] borrowAmts; + uint256[] supplyAmts; + address[] borrowTokens; + address[] supplyTokens; + CTokenInterface[] borrowCtokens; + CTokenInterface[] supplyCtokens; + address[] supplyCtokensAddr; + address[] borrowCtokensAddr; + } + + struct ImportInputData { + address userAccount; + string[] supplyIds; + string[] borrowIds; + } + + /** + * @dev enter compound market + * @param _cotkens array of ctoken addresses to enter compound market + */ + function _enterMarkets(address[] memory _cotkens) internal { + troller.enterMarkets(_cotkens); + } +} + +contract CompoundHelper is Helpers { + /** + * @notice fetch the borrow details of the user + * @dev approve the cToken to spend (borrowed amount of) tokens to allow for repaying later + * @param _importInputData the struct containing borrowIds of the users borrowed tokens + * @param data struct used to store the final data on which the CompoundHelper contract functions operate + * @return ImportData the final value of param data + */ + function getBorrowAmounts( + ImportInputData memory _importInputData, + ImportData memory data + ) internal returns (ImportData memory) { + if (_importInputData.borrowIds.length > 0) { + // initialize arrays for borrow data + uint256 _length = _importInputData.borrowIds.length; + data.borrowTokens = new address[](_length); + data.borrowCtokens = new CTokenInterface[](_length); + data.borrowCtokensAddr = new address[](_length); + data.borrowAmts = new uint256[](_length); + + // populate the arrays with borrow tokens, cToken addresses and instances, and borrow amounts + for (uint256 i; i < _length; i++) { + (address _token, address _cToken) = compMapping.getMapping( + _importInputData.borrowIds[i] + ); + + require( + _token != address(0) && _cToken != address(0), + "ctoken mapping not found" + ); + + data.cTokens[i] = _cToken; + + data.borrowTokens[i] = _token; + data.borrowCtokens[i] = CTokenInterface(_cToken); + data.borrowCtokensAddr[i] = _cToken; + data.borrowAmts[i] = data.borrowCtokens[i].borrowBalanceCurrent( + _importInputData.userAccount + ); + + // give the resp. cToken address approval to spend tokens + if (_token != ethAddr && data.borrowAmts[i] > 0) { + // will be required when repaying the borrow amount on behalf of the user + TokenInterface(_token).approve(_cToken, data.borrowAmts[i]); + } + } + } + return data; + } + + /** + * @notice fetch the supply details of the user + * @dev only reads data from blockchain hence view + * @param _importInputData the struct containing supplyIds of the users supplied tokens + * @param data struct used to store the final data on which the CompoundHelper contract functions operate + * @return ImportData the final value of param data + */ + function getSupplyAmounts( + ImportInputData memory _importInputData, + ImportData memory data + ) internal view returns (ImportData memory) { + // initialize arrays for supply data + uint256 _length = _importInputData.supplyIds.length; + data.supplyTokens = new address[](_length); + data.supplyCtokens = new CTokenInterface[](_length); + data.supplyCtokensAddr = new address[](_length); + data.supplyAmts = new uint256[](_length); + + // populate arrays with supply data (supply tokens address, cToken addresses, cToken instances and supply amounts) + for (uint256 i; i < _length; i++) { + (address _token, address _cToken) = compMapping.getMapping( + _importInputData.supplyIds[i] + ); + + require( + _token != address(0) && _cToken != address(0), + "ctoken mapping not found" + ); + + uint256 _supplyIndex = add(i, _importInputData.borrowIds.length); + data.cTokens[_supplyIndex] = _cToken; + + data.supplyTokens[i] = _token; + data.supplyCtokens[i] = CTokenInterface(_cToken); + data.supplyCtokensAddr[i] = (_cToken); + data.supplyAmts[i] = data.supplyCtokens[i].balanceOf( + _importInputData.userAccount + ); + } + return data; + } + + /** + * @notice repays the debt taken by user on Compound on its behalf to free its collateral for transfer + * @dev uses the cEth contract for ETH repays, otherwise the general cToken interface + * @param _userAccount the user address for which debt is to be repayed + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions + * @param _borrowAmts array containing the amount borrowed for each token + */ + function _repayUserDebt( + address _userAccount, + CTokenInterface[] memory _cTokenContracts, + uint256[] memory _borrowAmts + ) internal { + for (uint256 i; i < _cTokenContracts.length; i++) { + if (_borrowAmts[i] > 0) { + if (address(_cTokenContracts[i]) == address(cEth)) + cEth.repayBorrowBehalf{ value: _borrowAmts[i] }( + _userAccount + ); + else + require( + _cTokenContracts[i].repayBorrowBehalf( + _userAccount, + _borrowAmts[i] + ) == 0, + "repayOnBehalf-failed" + ); + } + } + } + + /** + * @notice used to transfer user's supply position on Compound to DSA + * @dev uses the transferFrom token in cToken contracts to transfer positions, requires approval from user first + * @param _userAccount address of the user account whose position is to be transferred + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has supply positions + * @param _amts array containing the amount supplied for each token + */ + function _transferTokensToDsa( + address _userAccount, + CTokenInterface[] memory _cTokenContracts, + uint256[] memory _amts + ) internal { + for (uint256 i; i < _cTokenContracts.length; i++) + if (_amts[i] > 0) + require( + _cTokenContracts[i].transferFrom( + _userAccount, + address(this), + _amts[i] + ), + "ctoken-transfer-failed-allowance?" + ); + } + + /** + * @notice borrows the user's debt positions from Compound via DSA, so that its debt positions get imported to DSA + * @dev actually borrow some extra amount than the original position to cover the flash loan fee + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions + * @param _amts array containing the amounts the user had borrowed originally from Compound plus the flash loan fee + * @param _flashLoanFees flash loan fee (in percentage and scaled up to 10**2) + */ + function _borrowDebtPosition( + CTokenInterface[] memory _cTokenContracts, + uint256[] memory _amts, + uint256[] memory _flashLoanFees + ) internal { + for (uint256 i; i < _cTokenContracts.length; i++) + if (_amts[i] > 0) + require( + _cTokenContracts[i].borrow( + add(_amts[i], _flashLoanFees[i]) + ) == 0, + "borrow-failed-collateral?" + ); + } +} diff --git a/contracts/mainnet/connectors/compound-import/interface.sol b/contracts/mainnet/connectors/compound-import/interface.sol new file mode 100644 index 00000000..5f9bf98d --- /dev/null +++ b/contracts/mainnet/connectors/compound-import/interface.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; + +interface TokenInterface { + function balanceOf(address) external view returns (uint256); + + function allowance(address, address) external view returns (uint256); + + function approve(address, uint256) external; + + function transfer(address, uint256) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); +} + +interface CTokenInterface { + function mint(uint256 mintAmount) external returns (uint256); + + function redeem(uint256 redeemTokens) external returns (uint256); + + function borrow(uint256 borrowAmount) external returns (uint256); + + function repayBorrow(uint256 repayAmount) external returns (uint256); + + function repayBorrowBehalf(address borrower, uint256 repayAmount) + external + returns (uint256); // For ERC20 + + function liquidateBorrow( + address borrower, + uint256 repayAmount, + address cTokenCollateral + ) external returns (uint256); + + function borrowBalanceCurrent(address account) external returns (uint256); + + function redeemUnderlying(uint256 redeemAmount) external returns (uint256); + + function exchangeRateCurrent() external returns (uint256); + + function balanceOf(address owner) external view returns (uint256 balance); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); + + function allowance(address, address) external view returns (uint256); +} + +interface CETHInterface { + function mint() external payable; + + function repayBorrow() external payable; + + function repayBorrowBehalf(address borrower) external payable; + + function liquidateBorrow(address borrower, address cTokenCollateral) + external + payable; +} + +interface ComptrollerInterface { + function enterMarkets(address[] calldata cTokens) + external + returns (uint256[] memory); + + function exitMarket(address cTokenAddress) external returns (uint256); + + function getAssetsIn(address account) + external + view + returns (address[] memory); + + function getAccountLiquidity(address account) + external + view + returns ( + uint256, + uint256, + uint256 + ); +} + +interface CompoundMappingInterface { + function cTokenMapping(string calldata tokenId) + external + view + returns (address); + + function getMapping(string calldata tokenId) + external + view + returns (address, address); +} diff --git a/contracts/mainnet/connectors/compound-import/main.sol b/contracts/mainnet/connectors/compound-import/main.sol new file mode 100644 index 00000000..9ae13f30 --- /dev/null +++ b/contracts/mainnet/connectors/compound-import/main.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; +pragma experimental ABIEncoderV2; + +/** + * @title Compound-Import. + * @dev Lending & Borrowing. + */ + +import { TokenInterface, AccountInterface } from "../../common/interfaces.sol"; +import { CompoundHelper } from "./helpers.sol"; +import { Events } from "./events.sol"; + +contract CompoundImportResolver is CompoundHelper { + /** + * @notice this function performs the import of user's Compound positions into its DSA + * @dev called internally by the importCompound and migrateCompound functions + * @param _importInputData the struct containing borrowIds of the users borrowed tokens + * @param _flashLoanFees list of flash loan fees + */ + function _importCompound( + ImportInputData memory _importInputData, + uint256[] memory _flashLoanFees + ) internal returns (string memory _eventName, bytes memory _eventParam) { + require( + AccountInterface(address(this)).isAuth( + _importInputData.userAccount + ), + "user-account-not-auth" + ); + + require(_importInputData.supplyIds.length > 0, "0-length-not-allowed"); + + ImportData memory data; + + uint256 _length = add( + _importInputData.supplyIds.length, + _importInputData.borrowIds.length + ); + data.cTokens = new address[](_length); + + // get info about all borrowings and lendings by the user on Compound + data = getBorrowAmounts(_importInputData, data); + data = getSupplyAmounts(_importInputData, data); + + _enterMarkets(data.cTokens); + + // pay back user's debt using flash loan funds + _repayUserDebt( + _importInputData.userAccount, + data.borrowCtokens, + data.borrowAmts + ); + + // transfer user's tokens to DSA + _transferTokensToDsa( + _importInputData.userAccount, + data.supplyCtokens, + data.supplyAmts + ); + + // borrow the earlier position from Compound with flash loan fee added + _borrowDebtPosition( + data.borrowCtokens, + data.borrowAmts, + _flashLoanFees + ); + + _eventName = "LogCompoundImport(address,address[],string[],string[],uint256[],uint256[])"; + _eventParam = abi.encode( + _importInputData.userAccount, + data.cTokens, + _importInputData.supplyIds, + _importInputData.borrowIds, + data.supplyAmts, + data.borrowAmts + ); + } + + /** + * @notice import Compound position of the address passed in as userAccount + * @dev internally calls _importContract to perform the actual import + * @param _userAccount address of user whose position is to be imported to DSA + * @param _supplyIds Ids of all tokens the user has supplied to Compound + * @param _borrowIds Ids of all token borrowed by the user + * @param _flashLoanFees list of flash loan fees + */ + function importCompound( + address _userAccount, + string[] memory _supplyIds, + string[] memory _borrowIds, + uint256[] memory _flashLoanFees + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ImportInputData memory inputData = ImportInputData({ + userAccount: _userAccount, + supplyIds: _supplyIds, + borrowIds: _borrowIds + }); + + (_eventName, _eventParam) = _importCompound(inputData, _flashLoanFees); + } +} + +contract ConnectV2CompoundImport is CompoundImportResolver { + string public constant name = "Compound-Import-v2"; +} diff --git a/scripts/constant/abi/connectors/instapool-c.json b/scripts/constant/abi/connectors/instapool-c.json new file mode 100644 index 00000000..8102f825 --- /dev/null +++ b/scripts/constant/abi/connectors/instapool-c.json @@ -0,0 +1,114 @@ +[ + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "token", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "tokenAmt", "type": "uint256" } + ], + "name": "LogFlashBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address[]", "name": "token", "type": "address[]" }, + { "indexed": false, "internalType": "uint256[]", "name": "tokenAmts", "type": "uint256[]" } + ], + "name": "LogFlashMultiBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address[]", "name": "token", "type": "address[]" }, + { "indexed": false, "internalType": "uint256[]", "name": "tokenAmts", "type": "uint256[]" } + ], + "name": "LogFlashMultiPayback", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "token", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "tokenAmt", "type": "uint256" } + ], + "name": "LogFlashPayback", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amt", "type": "uint256" }, + { "internalType": "uint256", "name": "route", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "bytes", "name": "extraData", "type": "bytes" } + ], + "name": "flashBorrowAndCast", + "outputs": [ + { "internalType": "string", "name": "_eventName", "type": "string" }, + { "internalType": "bytes", "name": "_eventParam", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "tokens_", "type": "address[]" }, + { "internalType": "uint256[]", "name": "amts_", "type": "uint256[]" }, + { "internalType": "uint256", "name": "route", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "bytes", "name": "extraData", "type": "bytes" } + ], + "name": "flashMultiBorrowAndCast", + "outputs": [ + { "internalType": "string", "name": "_eventName", "type": "string" }, + { "internalType": "bytes", "name": "_eventParam", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "tokens_", "type": "address[]" }, + { "internalType": "uint256[]", "name": "amts_", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "getIds", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "setIds", "type": "uint256[]" } + ], + "name": "flashMultiPayback", + "outputs": [ + { "internalType": "string", "name": "_eventName", "type": "string" }, + { "internalType": "bytes", "name": "_eventParam", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amt", "type": "uint256" }, + { "internalType": "uint256", "name": "getId", "type": "uint256" }, + { "internalType": "uint256", "name": "setId", "type": "uint256" } + ], + "name": "flashPayback", + "outputs": [ + { "internalType": "string", "name": "_eventName", "type": "string" }, + { "internalType": "bytes", "name": "_eventParam", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "instaPool", + "outputs": [{ "internalType": "contract InstaFlashV4Interface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/scripts/constant/abis.ts b/scripts/constant/abis.ts index 43097ba5..c2a70702 100644 --- a/scripts/constant/abis.ts +++ b/scripts/constant/abis.ts @@ -1,15 +1,16 @@ export const abis: Record = { core: { connectorsV2: require("./abi/core/connectorsV2.json"), - instaIndex: require("./abi/core/instaIndex.json"), + instaIndex: require("./abi/core/instaIndex.json") }, connectors: { "Basic-v1": require("./abi/connectors/basic.json"), basic: require("./abi/connectors/basic.json"), auth: require("./abi/connectors/auth.json"), "INSTAPOOL-A": require("./abi/connectors/instapool.json"), + "INSTAPOOL-C": require("./abi/connectors/instapool-c.json") }, basic: { - erc20: require("./abi/basics/erc20.json"), - }, + erc20: require("./abi/basics/erc20.json") + } }; diff --git a/test/mainnet/compound-import/compound-import.test.ts b/test/mainnet/compound-import/compound-import.test.ts new file mode 100644 index 00000000..fb4585f5 --- /dev/null +++ b/test/mainnet/compound-import/compound-import.test.ts @@ -0,0 +1,254 @@ +import { expect, should } from "chai"; +import hre, { ethers, waffle } from "hardhat"; +import type { Signer, Contract } from "ethers"; +import { BigNumber } from "bignumber.js"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { addresses } from "../../../scripts/tests/mainnet/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 { ConnectV2CompoundImport__factory } from "../../../typechain"; +const { provider } = waffle; + +const cEthAddress = "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5"; +const cDaiAddress = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"; +const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; +const comptrollerAddress = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B"; + +describe("Import Compound", function () { + const connectorName = "COMPOUND-IMPORT-X"; + + const cEthAbi = [ + { + constant: false, + inputs: [], + name: "mint", + outputs: [], + payable: true, + stateMutability: "payable", + type: "function", + signature: "0x1249c58b" + }, + { + constant: true, + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function" + }, + { + constant: false, + inputs: [], + name: "exchangeRateCurrent", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "nonpayable", + type: "function" + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "usr", type: "address" }, + { internalType: "uint256", name: "wad", type: "uint256" } + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function" + } + ]; + + const cDaiAbi = [ + { + constant: false, + inputs: [ + { + internalType: "uint256", + name: "borrowAmount", + type: "uint256" + } + ], + name: "borrow", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + signature: "0xc5ebeaec" + }, + { + constant: false, + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "borrowBalanceCurrent", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "nonpayable", + type: "function" + } + ]; + + const comptrollerAbi = [ + { + constant: false, + inputs: [ + { + internalType: "address[]", + name: "cTokens", + type: "address[]" + } + ], + name: "enterMarkets", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]" + } + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + signature: "0xc2998238" + } + ]; + + let cEth: Contract, cDai: Contract, comptroller, Dai: any; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 14441991 + } + } + ] + }); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2CompoundImport__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + + cEth = new ethers.Contract(cEthAddress, cEthAbi); + cDai = new ethers.Contract(cDaiAddress, cDaiAbi); + Dai = new ethers.Contract(daiAddress, abis.basic.erc20); + comptroller = new ethers.Contract(comptrollerAddress, comptrollerAbi); + + // deposit ether to Compound: ETH-A + await cEth.connect(wallet0).mint({ + value: parseEther("9") + }); + + // enter markets with deposits + const cTokens = [cEth.address]; + await comptroller.connect(wallet0).enterMarkets(cTokens); + + // borrow dai from Compound: DAI-A + await cDai.connect(wallet0).borrow(parseUnits("100")); + }); + + describe("Deployment", async () => { + it("Should set correct name", async () => { + expect(await connector.name()).to.eq("Compound-Import-v2"); + }); + }); + + describe("checks", async () => { + it("Should check user COMPOUND position", async () => { + const ethExchangeRate = (await cEth.connect(wallet0).callStatic.exchangeRateCurrent()) / 1e28; + expect(new BigNumber(await cEth.connect(wallet0).balanceOf(wallet0.address)).dividedBy(1e8).toFixed(0)).to.eq( + new BigNumber(9).dividedBy(ethExchangeRate).toFixed(0) + ); + expect(await Dai.connect(wallet0).balanceOf(wallet0.address)).to.eq("100000000000000000000"); + }); + }); + + describe("DSA wallet setup", async () => { + it("Should build DSA v2", async () => { + dsaWallet0 = await buildDSAv2(wallet0.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("Compound position migration", async () => { + it("Should migrate Compound position", async () => { + const tx0 = await cEth + .connect(wallet0) + .approve(dsaWallet0.address, await cEth.connect(wallet0).balanceOf(wallet0.address)); + + await tx0.wait(); + + // const amount0 = await cDai.connect(wallet0).callStatic.borrowBalanceCurrent(wallet0.address); + const amount0 = new BigNumber("100000007061117456728"); + const amount = new BigNumber(amount0.toString()).multipliedBy(5).dividedBy(1e4); + + const amountWithFee = amount0.plus(amount); + const flashSpells = [ + { + connector: "COMPOUND-IMPORT-X", + method: "importCompound", + args: [wallet0.address, ["ETH-A"], ["DAI-A"], [amount.toFixed(0)]] + }, + { + connector: "INSTAPOOL-C", + method: "flashPayback", + args: [daiAddress, amountWithFee.toFixed(0), 0, 0] + } + ]; + + const spells = [ + { + connector: "INSTAPOOL-C", + method: "flashBorrowAndCast", + args: [daiAddress, amount0.toString(), 5, encodeFlashcastData(flashSpells), "0x"] + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet0.address); + const receipt = await tx.wait(); + }); + + it("Should check DSA COMPOUND position", async () => { + const ethExchangeRate = (await cEth.connect(wallet0).callStatic.exchangeRateCurrent()) / 1e28; + expect(new BigNumber(await cEth.connect(wallet0).balanceOf(dsaWallet0.address)).dividedBy(1e8).toFixed(0)).to.eq( + new BigNumber(9).dividedBy(ethExchangeRate).toFixed(0) + ); + }); + }); +});