diff --git a/ConnectGelatoDebtBridge.sol b/ConnectGelatoDebtBridge.sol deleted file mode 100644 index 9ca81da..0000000 --- a/ConnectGelatoDebtBridge.sol +++ /dev/null @@ -1,531 +0,0 @@ -// "SPDX-License-Identifier: UNLICENSED" -pragma solidity 0.6.12; -pragma experimental ABIEncoderV2; - -import "./IMemoryInterface.sol"; -import "./DSMath.sol"; - -interface ConnectorInterface { - function connectorID() external view returns (uint256 _type, uint256 _id); - - function name() external view returns (string memory); -} - -interface OracleAggregator { - function getMakerTokenPrice(string memory _pair) - external - view - returns (uint256); -} - -interface GelatoGasPriceOracle { - function latestAnswer() external view returns (int256); -} - -interface IMakerResolver { - struct VaultData { - uint256 id; - address owner; - string colType; - uint256 collateral; - uint256 art; - uint256 debt; - uint256 liquidatedCol; - uint256 borrowRate; - uint256 colPrice; - uint256 liquidationRatio; - address vaultAddress; - } - - function getVaultById(uint256 id) external view returns (VaultData memory); -} - -interface ICompoundResolver { - struct CompData { - uint256 tokenPriceInEth; - uint256 tokenPriceInUsd; - uint256 exchangeRateStored; - uint256 balanceOfUser; - uint256 borrowBalanceStoredUser; - uint256 supplyRatePerBlock; - uint256 borrowRatePerBlock; - } - - function getCompoundData(address owner, address[] memory cAddress) - external - view - returns (CompData[] memory); -} - -interface IAaveResolver { - struct AaveTokenData { - uint256 ltv; - uint256 threshold; - bool usageAsCollEnabled; - bool borrowEnabled; - bool stableBorrowEnabled; - bool isActive; - } - - struct AaveUserTokenData { - uint256 tokenPriceInEth; - uint256 tokenPriceInUsd; - uint256 supplyBalance; - uint256 borrowBalance; - uint256 borrowFee; - uint256 supplyRate; - uint256 borrowRate; - uint256 borrowModal; - AaveTokenData aaveTokenData; - } - - struct AaveUserData { - uint256 totalSupplyETH; - uint256 totalCollateralETH; - uint256 totalBorrowsETH; - uint256 totalFeesETH; - uint256 availableBorrowsETH; - uint256 currentLiquidationThreshold; - uint256 ltv; - uint256 healthFactor; - uint256 ethPriceInUsd; - } - - function getPosition(address user, address[] memory tokens) - external - view - returns (AaveUserTokenData[] memory, AaveUserData memory); -} - -abstract contract Helpers is ConnectorInterface, DSMath { - uint256 internal _id; - - /** - * @dev Return ethereum address - */ - function _getAddressETH() internal pure returns (address) { - return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // ETH Address - } - - /** - * @dev Return Memory Variable Address - */ - function _getMemoryAddr() internal pure returns (address) { - return 0x8a5419CfC711B2343c17a6ABf4B2bAFaBb06957F; // InstaMemory Address - } - - /** - * @dev Set Uint value in InstaMemory Contract. - */ - function _setUint(uint256 setId, uint256 val) internal { - if (setId != 0) IMemoryInterface(_getMemoryAddr()).setUint(setId, val); - } - - /** - * @dev Get Uint value from InstaMemory Contract. - */ - function _getUint(uint256 getId, uint256 val) - internal - returns (uint256 returnVal) - { - returnVal = getId == 0 - ? val - : IMemoryInterface(_getMemoryAddr()).getUint(getId); - } - - /** - * @dev Connector Details - */ - function connectorID() - public - view - override - returns (uint256 _type, uint256 _iD) - { - (_type, _iD) = (1, _id); // Should put specific value. - } - - function _stringToBytes32(string memory str) - internal - pure - returns (bytes32 result) - { - require(bytes(str).length != 0, "String-Empty"); - // solium-disable-next-line security/no-inline-assembly - assembly { - result := mload(add(str, 32)) - } - } -} - -abstract contract ConnectGelatoDebtBridgeHelpers is Helpers { - function _getMakerResolver() internal pure returns (address) { - return 0x0A7008B38E7015F8C36A49eEbc32513ECA8801E5; - } - - function _getCompoundResolver() internal pure returns (address) { - return 0x1f22D77365d8BFE3b901C33C83C01B584F946617; - } - - function _getAaveResolver() internal pure returns (address) { - return 0xe04Cd009fF68628BC663058dDAA7E5Bf7979BEaF; - } - - function _getGelatoGasPriceOracle() internal pure returns (address) { - return 0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C; - } - - function _getGasPrice() internal view returns (uint256) { - return - uint256( - GelatoGasPriceOracle(_getGelatoGasPriceOracle()).latestAnswer() - ); - } -} - -abstract contract ConnectGelatoDebtBridgeResolver is - ConnectGelatoDebtBridgeHelpers -{ - mapping(address => address) internal _priceFeeds; - - function getMakerVault(uint256 _vaultID) - public - view - returns (IMakerResolver.VaultData memory) - { - // call maker resolver. - return IMakerResolver(_getMakerResolver()).getVaultById(_vaultID); - } - - function getMakerVaultDebt(uint256 _vaultID) public view returns (uint256) { - return getMakerVault(_vaultID).debt; - } - - function getMakerVaultCollateralBalance(uint256 _vaultID) - public - view - returns (uint256) - { - return getMakerVault(_vaultID).collateral; - } - - function getMakerVaultCollateralType(uint256 _vaultID) - public - view - returns (string memory) - { - return getMakerVault(_vaultID).colType; - } - - function getCompoundData(address _owner, address _cAddress) - public - view - returns (ICompoundResolver.CompData memory) - { - address[] memory cAddressArray; - cAddressArray[0] = _cAddress; - return - ICompoundResolver(_getCompoundResolver()).getCompoundData( - _owner, - cAddressArray - )[0]; - } - - function getCompoundDebt(address _owner, address _cAddress) - public - view - returns (uint256) - { - return getCompoundData(_owner, _cAddress).borrowBalanceStoredUser; - } - - function getCompoundCollateralBalance(address _owner, address _cAddress) - public - view - returns (uint256) - { - return getCompoundData(_owner, _cAddress).balanceOfUser; - } - - function getAaveTokenData(address _owner, address _atoken) - public - view - returns ( - IAaveResolver.AaveUserTokenData memory, - IAaveResolver.AaveUserData memory - ) - { - address[] memory aTokenArray; - aTokenArray[0] = _atoken; - ( - IAaveResolver.AaveUserTokenData[] memory tokensData, - IAaveResolver.AaveUserData memory etherUserData - ) = IAaveResolver(_getAaveResolver()).getPosition(_owner, aTokenArray); - return (tokensData[0], etherUserData); - } - - function getAaveTokenDebt(address _owner, address _atoken) - public - view - returns (uint256) - { - (IAaveResolver.AaveUserTokenData memory tokenData, ) = getAaveTokenData( - _owner, - _atoken - ); - return tokenData.supplyBalance; - } - - function getAaveTokenCollateralBalance(address _owner, address _atoken) - public - view - returns (uint256) - { - (IAaveResolver.AaveUserTokenData memory tokenData, ) = getAaveTokenData( - _owner, - _atoken - ); - return tokenData.borrowBalance; - } -} - -contract ConnectGelatoDebtBridge is ConnectGelatoDebtBridgeResolver { - // Constant name must be in capitalized SNAKE_CASE - // solhint-disable-next-line - string public constant override name = "GelatoDebtBridge-v1.0"; - uint256 public constant GASLIMIT = 1933090 + (19331 * 2); // 1933080 + ~2% (Estimated Value) - address public immutable oracleAggregator; - - constructor(uint256 _iD, address _oracleAggregator) public { - _id = _iD; - oracleAggregator = _oracleAggregator; - } - - /// @notice Write in instaMemory the needed values for doing full refinancing between makerDAO and Compound. - /// @param _vaultID is the id of the makerDAO vault. - /// @param _getID Id for writting in instaMemory. - /// @param _setID Id for loading from instaMemory. - function fullRefinanceMakerToCompound( - uint256 _vaultID, - uint256 _getID, - uint256 _setID - ) external payable { - uint256 fees = _getFees(); // get Fees - uint256 paybackAmount = getMakerVaultDebt(_vaultID); - uint256 collateralToWithdraw = getMakerVaultCollateralBalance(_vaultID); - - _setUint(100, paybackAmount); - _setUint(101, paybackAmount); // payback maker - _setUint(102, collateralToWithdraw); // withdraw maker - _setUint(103, _sub(collateralToWithdraw, fees)); // deposit compound - _setUint(104, paybackAmount); // borrow compound - _setUint(105, fees); // pay the provider - } - - /// @notice Write in instaMemory the needed values for doing partial refinancing between makerDAO and Compound. - /// @param _vaultID is the id of the makerDAO vault. - /// @param _vaultCollateralizationRatio is the collateralization ratio wanted by the client. - /// @param _compPosCollateralizationRatio is the collateralization ratio wanted by the client. - /// @param _pair crypto currency pair used (collateral token/ borrowed token). - /// @param _getID Id for writting in instaMemory. - /// @param _setID Id for loading from instaMemory. - function partialRefinanceMakerToCompound( - uint256 _vaultID, - uint256 _vaultCollateralizationRatio, // should be in ray because maker use ray standard - uint256 _compPosCollateralizationRatio, // should be in wad because compound use wad standard - string memory _pair, - uint256 _getID, - uint256 _setID - ) external payable { - ( - uint256 paybackAmount, - uint256 collateralToWithdraw, - uint256 fees - ) = debtBridgeCompute( - _vaultID, - _vaultCollateralizationRatio, - _compPosCollateralizationRatio, - _pair - ); - - _setUint(100, paybackAmount); - _setUint(101, paybackAmount); // payback maker - _setUint(102, _add(collateralToWithdraw, fees)); // withdraw maker - _setUint(103, collateralToWithdraw); // deposit compound - _setUint(104, paybackAmount); // borrow compound - _setUint(105, fees); // pay the provider - } - - // Price Oracle - - function debtBridgeCompute( - uint256 _vaultID, - uint256 _vaultLiquidationRatio, // should be in ray because maker use ray standard - uint256 _compPosLiquidationRatio, // should be in wad because compound use wad standard - string memory _pair - ) - public - view - returns ( - uint256 paybackAmount, - uint256 collateralToWithdraw, - uint256 fees - ) - { - uint256 latestPrice = _getLatestPrice(_pair); - // uint256 fees = mul(GASLIMIT, wmul(_getGasPrice(), latestPrice)); - fees = _getFees(); - - uint256 debt = getMakerVaultDebt(_vaultID); - uint256 collateral = _wmul( - _sub(getMakerVaultCollateralBalance(_vaultID), fees), - latestPrice - ); - - collateralToWithdraw = wcollateralToWithdraw( - _vaultLiquidationRatio, - _compPosLiquidationRatio, - collateral, - debt, - latestPrice - ); - paybackAmount = wborrowedTokenToPayback( - _vaultLiquidationRatio, - _compPosLiquidationRatio, - collateral, - debt - ); - } - - function _getFees() internal view returns (uint256 gasCost) { - gasCost = _mul(GASLIMIT, _getGasPrice()); - } - - function _getLatestPrice(string memory _pair) - internal - view - returns (uint256) - { - return OracleAggregator(oracleAggregator).getMakerTokenPrice(_pair); - } - - /// Computation in ray - /// @notice return the amount of collateral we need to withdraw during the debt refinancing in ray standard. - /// @param _p1LiqRatio the liquidation ratio of protocol 1. - /// @param _p2LiqRatio the liquidation ratio of protocol 2. - /// @param _col token1 collateral to put on protocol 1. - /// @param _bor amount of borrowed token2 on protocol 1. - /// @param _colPrice price of the collateral. - /// @return collateral to withdraw in ray standard - function _rcollateralToWithdraw( - uint256 _p1LiqRatio, - uint256 _p2LiqRatio, - uint256 _col, - uint256 _bor, - uint256 _colPrice - ) internal pure returns (uint256) { - return - _rdiv( - _sub( - _col, - _rdiv( - _sub( - _rmul(_p1LiqRatio, _col), - _rmul(_p1LiqRatio, _rmul(_p2LiqRatio, _bor)) - ), - _sub(_p1LiqRatio, _p2LiqRatio) - ) - ), - _colPrice - ); - } - - /// Computation in ray - /// @notice return the amount of borrowed token we need to payback during the debt refinancing in ray standard. - /// @param _p1LiqRatio the liquidation ratio of protocol 1. - /// @param _p2LiqRatio the liquidation ratio of protocol 2. - /// @param _col token1 collateral to put on protocol 1. - /// @param _bor amount of borrowed token2 on protocol 1. - /// @return amount of borrowed token to pay back in ray standard - function _rborrowedTokenToPayback( - uint256 _p1LiqRatio, - uint256 _p2LiqRatio, - uint256 _col, - uint256 _bor - ) internal pure returns (uint256) { - return - _sub( - _bor, - _rmul( - _rdiv(1e18, _p1LiqRatio), - _rdiv( - _sub( - _rmul(_p1LiqRatio, _col), - _rmul(_p1LiqRatio, _rmul(_p2LiqRatio, _bor)) - ), - _sub(_p1LiqRatio, _p2LiqRatio) - ) - ) - ); - } - - /// Computation in wad - /// @notice return the amount of collateral we need to withdraw during the debt refinancing in wad standard. - /// @param _p1LiqRatio the liquidation ratio of protocol 1. - /// @param _p2LiqRatio the liquidation ratio of protocol 2. - /// @param _col token1 collateral to put on protocol 1. - /// @param _bor amount of borrowed token2 on protocol 1. - /// @param _colPrice price of the collateral. - /// @return collateral to withdraw in wad standard - function wcollateralToWithdraw( - uint256 _p1LiqRatio, - uint256 _p2LiqRatio, - uint256 _col, - uint256 _bor, - uint256 _colPrice - ) public pure returns (uint256) { - return - _wdiv( - _sub( - _col, - _wdiv( - _sub( - _wmul(_p1LiqRatio, _col), - _wmul(_p1LiqRatio, _wmul(_p2LiqRatio, _bor)) - ), - _sub(_p1LiqRatio, _p2LiqRatio) - ) - ), - _colPrice - ); - } - - /// Computation in wad - /// @notice return the amount of borrowed token we need to payback during the debt refinancing in wad standard. - /// @param _p1LiqRatio the liquidation ratio of protocol 1. - /// @param _p2LiqRatio the liquidation ratio of protocol 2. - /// @param _col token1 collateral to put on protocol 1. - /// @param _bor amount of borrowed token2 on protocol 1. - /// @return amount of borrowed token to pay back in wad standard - function wborrowedTokenToPayback( - uint256 _p1LiqRatio, - uint256 _p2LiqRatio, - uint256 _col, - uint256 _bor - ) public pure returns (uint256) { - return - _sub( - _bor, - _wmul( - _wdiv(1e18, _p1LiqRatio), - _wdiv( - _sub( - _wmul(_p1LiqRatio, _col), - _wmul(_p1LiqRatio, _wmul(_p2LiqRatio, _bor)) - ), - _sub(_p1LiqRatio, _p2LiqRatio) - ) - ) - ); - } -} diff --git a/README.md b/README.md index bfde4f9..b9e218f 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,12 @@ Furtheremore the following contracts were added to showcase the automation of th To see for yourself check out the [contracts](./contracts) folder and make sure to check out `test/mv-DAI-DSR-Compound.test.js`, to see an end-to-end test showcasing the prototype. To do so follow the steps below: -#### Debt Bridge +## Debt Bridge -Debt Bridge is a finance process that aim to make the user position safer. InstaDapp DSA could use Gelato to automate this process. +Debt Bridge is a financial process that aim to make the user position safer. InstaDapp DSA could use Gelato to automate this process. + +### Full Refinancing from Maker's Vault to Compound. -**Full Refinancing from Maker's Vault to Compound.** Based on the [debt bridge](https://docs.instadapp.io/usecases/debt-bridge/) documentation of Instadapp, we automated this process by adding two connectors `ConnectGelatoDebtBridge`, `ConnectGelatoProviderPayment` and a Gelato condition contract. - `ConditionMakerVaultIsSafe.sol`: determine if a specific vault is on an unsafe position. diff --git a/contracts/connectors/ConnectGelatoDebtBridgeFromMaker.sol b/contracts/connectors/ConnectGelatoDebtBridgeFromMaker.sol index a66de99..ed1493b 100644 --- a/contracts/connectors/ConnectGelatoDebtBridgeFromMaker.sol +++ b/contracts/connectors/ConnectGelatoDebtBridgeFromMaker.sol @@ -270,6 +270,13 @@ contract ConnectGelatoDebtBridgeFromMaker is MakerResolver { __id = _id; } + /// @notice Get gas price from gelato Gas Price Oracle and multiply + /// this gas price by the estimated amount of needed for executing + /// the transaction + function _getFees() internal view returns (uint256 gasCost) { + gasCost = _mul(GAS_LIMIT, _getGasPrice()); + } + /// @notice Saves Data to InstaMemory that can be used for DebtBridge Maker->Compound /// @dev Use wad for colRatios. The user has no influence over setUint or getUint. /// @param _vaultId The id of the makerDAO vault. @@ -309,6 +316,27 @@ contract ConnectGelatoDebtBridgeFromMaker is MakerResolver { setUint(605, gasFeesPaidFromCol); // pay the Gelato Provider (TO DO: unsafe) } + /// @notice Save in instaMemory the needed values for doing full refinancing between makerDAO and Compound. + /// @param _vaultID The ID of the makerDAO vault. + // @param _getID Id for writting in instaMemory. + // @param _setID Id for loading from instaMemory. + function saveFullRefinanceFromMakerDataToMemory( + uint256 _vaultID, + uint256, /*_getId,*/ + uint256 /*_setId*/ + ) external payable { + uint256 fees = _getFees(); // get Fees + uint256 paybackAmount = getMakerVaultDebt(_vaultID); + uint256 collateralToWithdraw = getMakerVaultCollateralBalance(_vaultID); + + setUint(600, paybackAmount); + setUint(601, paybackAmount); // payback maker + setUint(602, collateralToWithdraw); // withdraw maker + setUint(603, _sub(collateralToWithdraw, fees)); // deposit compound + setUint(604, paybackAmount); // borrow compound + setUint(605, fees); // pay the provider + } + /// @notice Computes values needed for DebtBridge Maker->ProtocolB /// @dev Use wad for colRatios. /// @param _vaultId The id of the makerDAO vault. @@ -357,7 +385,7 @@ contract ConnectGelatoDebtBridgeFromMaker is MakerResolver { // TO DO: add fee mechanism for non-ETH collateral debt bridge // uint256 gasFeesPaidFromCol = _mul(GAS_LIMIT, wmul(_getGasPrice(), latestPrice)); - gasFeesPaidFromCol = _mul(GAS_LIMIT, _getGasPrice()); + gasFeesPaidFromCol = _getFees(); uint256 wPricedCol = wmul( _sub(getMakerVaultCollateralBalance(_vaultId), gasFeesPaidFromCol), diff --git a/test/2_Full-Refinance-External-Provider.test.js b/test/2_Full-Refinance-External-Provider.test.js new file mode 100644 index 0000000..8b2ea9c --- /dev/null +++ b/test/2_Full-Refinance-External-Provider.test.js @@ -0,0 +1,265 @@ +const {expect} = require("chai"); +const hre = require("hardhat"); +const {ethers} = hre; +const GelatoCoreLib = require("@gelatonetwork/core"); + +const Helper = require("./helpers/Full-Refinance-External-Provider.helper"); +const helper = new Helper(); + +describe("Debt Bridge with External Provider", function () { + this.timeout(0); + if (hre.network.name !== "hardhat") { + console.error("Test Suite is meant to be run on hardhat only"); + process.exit(1); + } + + let contracts; + let address; + let constants; + let ABI; + + // Payload Params for ConnectGelatoDebtBridgeFromMaker and ConditionMakerVaultUnsafe + let vaultId; + + // For TaskSpec and for Task + let spells = []; + + // Cross test var + let taskReceipt; + + before(async function () { + const result = await helper.setup(); + + address = result.address; + contracts = result.contracts; + vaultId = result.vaultId; + spells = result.spells; + + ABI = await helper.getABI(); + constants = await helper.getConstants(); + }); + + it("#1: DSA give Authorization to Gelato to execute action his behalf.", async function () { + //#region User give authorization to gelato to use his DSA on his behalf. + + // Instadapp DSA contract give the possibility to the user to delegate + // action by giving authorization. + // In this case user give authorization to gelato to execute + // task for him if needed. + + await contracts.dsa.cast( + [hre.network.config.ConnectAuth], + [ + await hre.run("abi-encode-withselector", { + abi: ABI.ConnectAuthABI, + functionname: "add", + inputs: [contracts.gelatoCore.address], + }), + ], + address.userAddress + ); + + expect(await contracts.dsa.isAuth(contracts.gelatoCore.address)).to.be.true; + + //#endregion + }); + + it("#2 : User submit Debt refinancing task if market move", async function () { + //#region User submit a Debt Refinancing task if market move against him + + // User submit the refinancing task if market move against him. + // So in this case if the maker vault go to the unsafe area + // the refinancing task will be executed and the position + // will be split on two position on maker and compound. + // It will be done through a algorithm that will optimize the + // total borrow rate. + + const conditionMakerVaultUnsafeObj = new GelatoCoreLib.Condition({ + inst: contracts.conditionMakerVaultUnsafe.address, + data: await contracts.conditionMakerVaultUnsafe.getConditionData( + vaultId, + contracts.priceOracleResolver.address, + await hre.run("abi-encode-withselector", { + abi: ABI.PriceOracleResolverABI, + functionname: "getMockPrice", + inputs: [address.userAddress], + }), + constants.MIN_COL_RATIO_MAKER + ), + }); + + // ======= GELATO TASK SETUP ====== + const refinanceIfCompoundBorrowIsBetter = new GelatoCoreLib.Task({ + conditions: [conditionMakerVaultUnsafeObj], + actions: spells, + }); + + const gelatoExternalProvider = new GelatoCoreLib.GelatoProvider({ + addr: address.providerAddress, + module: contracts.dsaProviderModule.address, + }); + + const expiryDate = 0; + + await expect( + contracts.dsa.cast( + [contracts.connectGelato.address], // targets + [ + await hre.run("abi-encode-withselector", { + abi: ABI.ConnectGelatoABI, + functionname: "submitTask", + inputs: [ + gelatoExternalProvider, + refinanceIfCompoundBorrowIsBetter, + expiryDate, + ], + }), + ], // datas + address.userAddress, // origin + { + gasLimit: 5000000, + } + ) + ).to.emit(contracts.gelatoCore, "LogTaskSubmitted"); + + taskReceipt = new GelatoCoreLib.TaskReceipt({ + id: await contracts.gelatoCore.currentTaskReceiptId(), + userProxy: contracts.dsa.address, + provider: gelatoExternalProvider, + tasks: [refinanceIfCompoundBorrowIsBetter], + expiryDate, + }); + + //#endregion + }); + + it("#2: Use Maker Compound refinancing if the maker vault become unsafe after a market move.", async function () { + // Steps + // Step 1: Market Move against the user (Mock) + // Step 2: Executor execute the user's task + + //#region Step 1 Market Move against the user (Mock) + + // Ether market price went from the current price to 250$ + + const gelatoGasPrice = await hre.run("fetchGelatoGasPrice"); + expect(gelatoGasPrice).to.be.lte(constants.GAS_PRICE_CEIL); + + // TO DO: base mock price off of real price data + await contracts.priceOracleResolver.setMockPrice( + ethers.utils.parseUnits("400", 18) + ); + + expect( + await contracts.gelatoCore + .connect(address.executorWallet) + .canExec(taskReceipt, constants.GAS_LIMIT, gelatoGasPrice) + ).to.be.equal("ConditionNotOk:MakerVaultNotUnsafe"); + + // TO DO: base mock price off of real price data + await contracts.priceOracleResolver.setMockPrice( + ethers.utils.parseUnits("250", 18) + ); + + expect( + await contracts.gelatoCore + .connect(address.executorWallet) + .canExec(taskReceipt, constants.GAS_LIMIT, gelatoGasPrice) + ).to.be.equal("OK"); + + //#endregion + + //#region Step 2 Executor execute the user's task + + // The market move make the vault unsafe, so the executor + // will execute the user's task to make the user position safe + // by a debt refinancing in compound. + + //#region EXPECTED OUTCOME + + const gasFeesPaidFromCol = ethers.utils + .parseUnits(String(1933090 + 19331 * 2), 0) + .mul(gelatoGasPrice); + const debtOnMakerBefore = await contracts.connectGelatoDebtBridgeFromMaker.getMakerVaultDebt( + vaultId + ); + const pricedCollateral = ( + await contracts.connectGelatoDebtBridgeFromMaker.getMakerVaultCollateralBalance( + vaultId + ) + ).sub(gasFeesPaidFromCol); + + //#endregion + const providerBalanceBeforeExecution = await address.providerWallet.getBalance(); + + await expect( + contracts.gelatoCore.connect(address.executorWallet).exec(taskReceipt, { + gasPrice: gelatoGasPrice, // Exectutor must use gelatoGasPrice (Chainlink fast gwei) + gasLimit: constants.GAS_LIMIT, + }) + ).to.emit(contracts.gelatoCore, "LogExecSuccess"); + + // 🚧 For Debugging: + // const txResponse2 = await gelatoCore + // .connect(providerWallet) + // .exec(taskReceipt, { + // gasPrice: gelatoGasPrice, + // gasLimit: GAS_LIMIT, + // }); + // const {blockHash} = await txResponse2.wait(); + // const logs = await ethers.provider.getLogs({blockHash}); + // const iFace = new ethers.utils.Interface(GelatoCoreLib.GelatoCore.abi); + // for (const log of logs) { + // console.log(iFace.parseLog(log).args.reason); + // } + // await GelatoCoreLib.sleep(10000); + + expect(await address.providerWallet.getBalance()).to.be.gt( + providerBalanceBeforeExecution + ); + + // compound position of DSA on cDai and cEth + const compoundPosition = await contracts.compoundResolver.getCompoundData( + contracts.dsa.address, + [contracts.cDaiToken.address, contracts.cEthToken.address] + ); + + // https://compound.finance/docs/ctokens#exchange-rate + // calculate cEth/ETH rate to convert back cEth to ETH + // for comparing with the withdrew Ether to the deposited one. + const exchangeRateCethToEth = (await contracts.cEthToken.getCash()) + .add(await contracts.cEthToken.totalBorrows()) + .sub(await contracts.cEthToken.totalReserves()) + .div(await contracts.cEthToken.totalSupply()); + + // Estimated amount to borrowed token should be equal to the actual one read on compound contracts + expect(debtOnMakerBefore).to.be.equal( + compoundPosition[0].borrowBalanceStoredUser + ); + + // Estimated amount of collateral should be equal to the actual one read on compound contracts + expect( + pricedCollateral.sub( + compoundPosition[1].balanceOfUser.mul(exchangeRateCethToEth) + ) + ).to.be.lt(ethers.utils.parseUnits("1", 12)); + + const debtOnMakerAfter = await contracts.connectGelatoDebtBridgeFromMaker.getMakerVaultDebt( + vaultId + ); + const collateralOnMakerAfter = await contracts.connectGelatoDebtBridgeFromMaker.getMakerVaultCollateralBalance( + vaultId + ); // in Ether. + + // We should not have borrowed DAI on maker or deposited ether on it. + expect(debtOnMakerAfter).to.be.equal(ethers.constants.Zero); + expect(collateralOnMakerAfter).to.be.equal(ethers.constants.Zero); + + // DSA contain 1000 DAI + expect( + await contracts.daiToken.balanceOf(contracts.dsa.address) + ).to.be.equal(constants.MAKER_INITIAL_DEBT); + + //#endregion + }); +}); diff --git a/test/helpers/Full-Refinance-External-Provider.helper.js b/test/helpers/Full-Refinance-External-Provider.helper.js new file mode 100644 index 0000000..8c4bf8a --- /dev/null +++ b/test/helpers/Full-Refinance-External-Provider.helper.js @@ -0,0 +1,642 @@ +const {expect} = require("chai"); +const hre = require("hardhat"); +const {ethers} = hre; +const GelatoCoreLib = require("@gelatonetwork/core"); + +const InstaIndex = require("../../pre-compiles/InstaIndex.json"); +const InstaList = require("../../pre-compiles/InstaList.json"); +const InstaAccount = require("../../pre-compiles/InstaAccount.json"); +const ConnectGelato = require("../../pre-compiles/ConnectGelato.json"); +const ConnectMaker = require("../../pre-compiles/ConnectMaker.json"); +const ConnectCompound = require("../../pre-compiles/ConnectCompound.json"); +const ConnectInstaPool = require("../../pre-compiles/ConnectInstaPool.json"); +const ConnectAuth = require("../../pre-compiles/ConnectAuth.json"); + +const InstaConnector = require("../../pre-compiles/InstaConnectors.json"); +const DssCdpManager = require("../../pre-compiles/DssCdpManager.json"); +const GetCdps = require("../../pre-compiles/GetCdps.json"); +const IERC20 = require("../../pre-compiles/IERC20.json"); +const CTokenInterface = require("../../pre-compiles/CTokenInterface.json"); +const CompoundResolver = require("../../pre-compiles/InstaCompoundResolver.json"); +const PriceOracleResolverABI = require("../../artifacts/contracts/resolvers/PriceOracleResolver.sol/PriceOracleResolver.json") + .abi; +const ConnectGelatoProviderPaymentABI = require("../../artifacts/contracts/connectors/ConnectGelatoProviderPayment.sol/ConnectGelatoProviderPayment.json") + .abi; +const ConnectGelatoDebtBridgeFromMakerABI = require("../../artifacts/contracts/connectors/ConnectGelatoDebtBridgeFromMaker.sol/ConnectGelatoDebtBridgeFromMaker.json") + .abi; + +const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const GAS_LIMIT = "4000000"; +const GAS_PRICE_CEIL = ethers.utils.parseUnits("1000", "gwei"); + +const MIN_COL_RATIO_MAKER = ethers.utils.parseUnits("3", 18); + +// TO DO: make dynamic based on real time Collateral Price and Ratios +const MAKER_INITIAL_ETH = ethers.utils.parseEther("10"); +const MAKER_INITIAL_DEBT = ethers.utils.parseUnits("1000", 18); + +// #endregion + +class Helper { + async address() { + let userWallet; + let userAddress; + let providerWallet; + let providerAddress; + let executorWallet; + let executorAddress; + + [userWallet, providerWallet, executorWallet] = await ethers.getSigners(); + userAddress = await userWallet.getAddress(); + providerAddress = await providerWallet.getAddress(); + executorAddress = await executorWallet.getAddress(); + + // Hardhat default accounts prefilled with 100 ETH + expect(await userWallet.getBalance()).to.be.gt( + ethers.utils.parseEther("10") + ); + + return { + userWallet: userWallet, + userAddress: userAddress, + providerWallet: providerWallet, + providerAddress: providerAddress, + executorWallet: executorWallet, + executorAddress: executorAddress, + }; + } + + async contracts() { + // Deployed instances + let connectGelato; + let connectMaker; + let connectInstaPool; + let connectCompound; + let instaIndex; + let instaList; + let dssCdpManager; + let getCdps; + let daiToken; + let gelatoCore; + let cDaiToken; + let cEthToken; + let instaMaster; + let instaConnectors; + let compoundResolver; + // Contracts to deploy and use for local testing + let conditionMakerVaultUnsafe; + let connectGelatoDebtBridgeFromMaker; + let connectGelatoProviderPayment; + let priceOracleResolver; + let dsaProviderModule; + + instaMaster = await ethers.provider.getSigner( + hre.network.config.InstaMaster + ); + + // ===== Get Deployed Contract Instance ================== + instaIndex = await ethers.getContractAt( + InstaIndex.abi, + hre.network.config.InstaIndex + ); + instaList = await ethers.getContractAt( + InstaList.abi, + hre.network.config.InstaList + ); + connectGelato = await ethers.getContractAt( + ConnectGelato.abi, + hre.network.config.ConnectGelato + ); + connectMaker = await ethers.getContractAt( + ConnectMaker.abi, + hre.network.config.ConnectMaker + ); + connectInstaPool = await ethers.getContractAt( + ConnectInstaPool.abi, + hre.network.config.ConnectInstaPool + ); + connectCompound = await ethers.getContractAt( + ConnectCompound.abi, + hre.network.config.ConnectCompound + ); + dssCdpManager = await ethers.getContractAt( + DssCdpManager.abi, + hre.network.config.DssCdpManager + ); + getCdps = await ethers.getContractAt( + GetCdps.abi, + hre.network.config.GetCdps + ); + daiToken = await ethers.getContractAt(IERC20.abi, hre.network.config.DAI); + gelatoCore = await ethers.getContractAt( + GelatoCoreLib.GelatoCore.abi, + hre.network.config.GelatoCore + ); + cDaiToken = await ethers.getContractAt( + CTokenInterface.abi, + hre.network.config.CDAI + ); + cEthToken = await ethers.getContractAt( + CTokenInterface.abi, + hre.network.config.CETH + ); + instaConnectors = await ethers.getContractAt( + InstaConnector.abi, + hre.network.config.InstaConnectors + ); + compoundResolver = await ethers.getContractAt( + CompoundResolver.abi, + hre.network.config.CompoundResolver + ); + + // ===== Deploy Needed Contract ================== + + const PriceOracleResolver = await ethers.getContractFactory( + "PriceOracleResolver" + ); + priceOracleResolver = await PriceOracleResolver.deploy(); + await priceOracleResolver.deployed(); + + const ConditionMakerVaultUnsafe = await ethers.getContractFactory( + "ConditionMakerVaultUnsafe" + ); + conditionMakerVaultUnsafe = await ConditionMakerVaultUnsafe.deploy(); + await conditionMakerVaultUnsafe.deployed(); + + const ConnectGelatoDebtBridgeFromMaker = await ethers.getContractFactory( + "ConnectGelatoDebtBridgeFromMaker" + ); + connectGelatoDebtBridgeFromMaker = await ConnectGelatoDebtBridgeFromMaker.deploy( + (await instaConnectors.connectorLength()).add(1) + ); + await connectGelatoDebtBridgeFromMaker.deployed(); + + const ConnectGelatoProviderPayment = await ethers.getContractFactory( + "ConnectGelatoProviderPayment" + ); + connectGelatoProviderPayment = await ConnectGelatoProviderPayment.deploy( + (await instaConnectors.connectorLength()).add(2) + ); + await connectGelatoProviderPayment.deployed(); + + const ProviderModuleDSA = await ethers.getContractFactory( + "ProviderModuleDSA" + ); + dsaProviderModule = await ProviderModuleDSA.deploy( + hre.network.config.GelatoCore, + connectGelatoProviderPayment.address + ); + await dsaProviderModule.deployed(); + + return { + connectGelato: connectGelato, + connectMaker: connectMaker, + connectInstaPool: connectInstaPool, + connectCompound: connectCompound, + instaIndex: instaIndex, + instaList: instaList, + dssCdpManager: dssCdpManager, + getCdps: getCdps, + daiToken: daiToken, + gelatoCore: gelatoCore, + cDaiToken: cDaiToken, + cEthToken: cEthToken, + instaMaster: instaMaster, + instaConnectors: instaConnectors, + compoundResolver: compoundResolver, + conditionMakerVaultUnsafe: conditionMakerVaultUnsafe, + connectGelatoDebtBridgeFromMaker: connectGelatoDebtBridgeFromMaker, + connectGelatoProviderPayment: connectGelatoProviderPayment, + priceOracleResolver: priceOracleResolver, + dsaProviderModule: dsaProviderModule, + dsa: ethers.constants.AddressZero, + }; + } + + async setup() { + let address = await this.address(); + let contracts = await this.contracts(); + let dsa; + let vaultId; + /////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////// Setup /////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////// + + // Gelato Testing environment setup. + // Step 1 : Add EUR/USD Maker Medianizer in the PriceOracleResolver + // Step 2 : Enable Debt Bridge Connector and Gelato Provider Payment Connector + // Step 3 : Executor Staking on Gelato + // Step 4 : Provider put some fund on gelato for paying future tasks executions + // Step 5 : Provider choose a executor + // Step 6 : Provider will add a module + // Step 7 : User create a DeFi Smart Account + // Step 8 : User open a Vault, put some ether on it and borrow some dai + // Step 9 : Provider should whitelist task + + //#region Step 1 Add EUR/USD Maker Medianizer in the PriceOracleResolver + + // PriceOracleResolver is a price feeder aggregator + // You will be able to query price from multiple source through this aggregator + // For the demo we add the ETH/USD Medianizer to the aggregator + // MakerDAO price oracle are called Medianizer + + // await priceOracleResolver.addOracle( + // ORACLE_MAKER_ETH_USD, + // ORACLE_MAKER_ETH_USD_ADDR, + // PRICE_ORACLE_MAKER_PAYLOAD + // ); + + //#endregion + + //#region Step 2 Enable Debt Bridge Connector and Gelato Provider Payment Connector + + // Debt Bridge Connector is used during refinancing of debt + // This Connect help the user to split a position in one protocol. + // to 2 protocol in a safe way. Both debt position will be safe. + + // Gelato Provider Payment Connector is used for paying the provider + // for task execution. So when futur task will be executed, through a self financing + // transaction (user will pay during the execution of the task) task will + // be executed. Improvind user experience. + + await address.userWallet.sendTransaction({ + to: hre.network.config.InstaMaster, + value: ethers.utils.parseEther("0.1"), + }); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [await contracts.instaMaster.getAddress()], + }); + + await contracts.instaConnectors + .connect(contracts.instaMaster) + .enable(contracts.connectGelatoDebtBridgeFromMaker.address); + + await contracts.instaConnectors + .connect(contracts.instaMaster) + .enable(contracts.connectGelatoProviderPayment.address); + + await hre.network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [await contracts.instaMaster.getAddress()], + }); + + expect( + await contracts.instaConnectors.isConnector([ + contracts.connectGelatoDebtBridgeFromMaker.address, + ]) + ).to.be.true; + expect( + await contracts.instaConnectors.isConnector([ + contracts.connectGelatoProviderPayment.address, + ]) + ).to.be.true; + + //#endregion + + //#region Step 3 Executor Staking on Gelato + + // For task execution provider will ask a executor to watch the + // blockchain for possible execution autorization given by + // the condition that user choose when submitting the task. + // And if all condition are meet executor will execute the task. + // For safety measure Gelato ask the executor to stake a minimum + // amount. + + await contracts.gelatoCore.connect(address.executorWallet).stakeExecutor({ + value: await contracts.gelatoCore.minExecutorStake(), + }); + + expect( + await contracts.gelatoCore.isExecutorMinStaked(address.executorAddress) + ).to.be.true; + + //#endregion + + //#region Step 4 Provider put some fund on gelato for paying future tasks executions + + // Provider put some funds in gelato system for paying the + // Executor when this one will execute task on behalf of the + // Provider. At each provider's task execution, some funds (approximatively + // the gas cost value) will be transfered to the Executor stake. + + const TASK_AUTOMATION_FUNDS = await contracts.gelatoCore.minExecProviderFunds( + GAS_LIMIT, + GAS_PRICE_CEIL + ); + + await expect( + contracts.gelatoCore + .connect(address.providerWallet) + .provideFunds(address.providerAddress, { + value: TASK_AUTOMATION_FUNDS, + }) + ).to.emit(contracts.gelatoCore, "LogFundsProvided"); + + expect( + await contracts.gelatoCore.providerFunds(address.providerAddress) + ).to.be.equal(TASK_AUTOMATION_FUNDS); + + //#endregion + + //#region Step 5 Provider choose a executor + + // Provider choose a executor who will execute futur task + // for the provider, it will be compensated by the provider. + + await expect( + contracts.gelatoCore + .connect(address.providerWallet) + .providerAssignsExecutor(address.executorAddress) + ).to.emit(contracts.gelatoCore, "LogProviderAssignedExecutor"); + + expect( + await contracts.gelatoCore.executorByProvider(address.providerAddress) + ).to.be.equal(address.executorAddress); + + //#endregion + + //#region Step 6 Provider will add a module + + // By adding a module the provider will format future task's + // payload by adding some specificity like his address to the + // Payment connector for receiving payment of User. + + await expect( + contracts.gelatoCore + .connect(address.providerWallet) + .addProviderModules([contracts.dsaProviderModule.address]) + ).to.emit(contracts.gelatoCore, "LogProviderModuleAdded"); + + expect( + await contracts.gelatoCore + .connect(address.providerWallet) + .isModuleProvided( + address.providerAddress, + contracts.dsaProviderModule.address + ) + ).to.be.true; + + //#endregion + + //#region Step 7 User create a DeFi Smart Account + + // User create a Instadapp DeFi Smart Account + // who give him the possibility to interact + // with a large list of DeFi protocol through one + // Proxy account. + + const dsaAccountCount = await contracts.instaList.accounts(); + + await expect( + contracts.instaIndex.build(address.userAddress, 1, address.userAddress) + ).to.emit(contracts.instaIndex, "LogAccountCreated"); + const dsaID = dsaAccountCount.add(1); + await expect(await contracts.instaList.accounts()).to.be.equal(dsaID); + + // Instantiate the DSA + dsa = await ethers.getContractAt( + InstaAccount.abi, + await contracts.instaList.accountAddr(dsaID) + ); + + contracts.dsa = dsa; + + //#endregion + + //#region Step 8 User open a Vault, put some ether on it and borrow some dai + + // User open a maker vault + // He deposit 10 Eth on it + // He borrow a 1000 DAI + + const openVault = await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "open", + inputs: ["ETH-A"], + }); + + await dsa.cast( + [hre.network.config.ConnectMaker], + [openVault], + address.userAddress + ); + + const cdps = await contracts.getCdps.getCdpsAsc( + contracts.dssCdpManager.address, + dsa.address + ); + vaultId = String(cdps.ids[0]); + expect(cdps.ids[0].isZero()).to.be.false; + + await dsa.cast( + [hre.network.config.ConnectMaker], + [ + await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "deposit", + inputs: [vaultId, MAKER_INITIAL_ETH, 0, 0], + }), + ], + address.userAddress, + { + value: MAKER_INITIAL_ETH, + } + ); + + await dsa.cast( + [hre.network.config.ConnectMaker], + [ + await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "borrow", + inputs: [vaultId, MAKER_INITIAL_DEBT, 0, 0], + }), + ], + address.userAddress + ); + + expect(await contracts.daiToken.balanceOf(dsa.address)).to.be.equal( + MAKER_INITIAL_DEBT + ); + + //#endregion + + //#region Step 9 Provider should whitelist task + + // By WhiteList task, the provider can constrain the type + // of task the user can submitting. + + //#region Actions + + let spells = []; + + const debtBridgeCalculationForFullRefinance = new GelatoCoreLib.Action({ + addr: contracts.connectGelatoDebtBridgeFromMaker.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectGelatoDebtBridgeFromMakerABI, + functionname: "saveFullRefinanceFromMakerDataToMemory", + inputs: [vaultId, 0, 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(debtBridgeCalculationForFullRefinance); + + const flashBorrow = new GelatoCoreLib.Action({ + addr: contracts.connectInstaPool.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectInstaPool.abi, + functionname: "flashBorrow", + inputs: [hre.network.config.DAI, 0, "600", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(flashBorrow); + + const paybackMaker = new GelatoCoreLib.Action({ + addr: contracts.connectMaker.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "payback", + inputs: [vaultId, 0, "601", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(paybackMaker); + + const withdrawMaker = new GelatoCoreLib.Action({ + addr: contracts.connectMaker.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "withdraw", + inputs: [vaultId, 0, "602", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(withdrawMaker); + + const depositCompound = new GelatoCoreLib.Action({ + addr: contracts.connectCompound.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectCompound.abi, + functionname: "deposit", + inputs: [ETH, 0, "603", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(depositCompound); + + const borrowCompound = new GelatoCoreLib.Action({ + addr: contracts.connectCompound.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectCompound.abi, + functionname: "borrow", + inputs: [hre.network.config.DAI, 0, "604", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(borrowCompound); + + const flashPayBack = new GelatoCoreLib.Action({ + addr: contracts.connectInstaPool.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectInstaPool.abi, + functionname: "flashPayback", + inputs: [hre.network.config.DAI, 0, 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(flashPayBack); + + const payProvider = new GelatoCoreLib.Action({ + addr: contracts.connectGelatoProviderPayment.address, + data: await hre.run("abi-encode-withselector", { + abi: ConnectGelatoProviderPaymentABI, + functionname: "payProvider", + inputs: [address.providerAddress, ETH, 0, "605", 0], + }), + operation: GelatoCoreLib.Operation.Delegatecall, + }); + + spells.push(payProvider); + + const gasPriceCeil = ethers.constants.MaxUint256; + + const connectGelatoDebtBridgeFromMakerTaskSpec = new GelatoCoreLib.TaskSpec( + { + conditions: [contracts.conditionMakerVaultUnsafe.address], + actions: spells, + gasPriceCeil, + } + ); + + await expect( + contracts.gelatoCore + .connect(address.providerWallet) + .provideTaskSpecs([connectGelatoDebtBridgeFromMakerTaskSpec]) + ).to.emit(contracts.gelatoCore, "LogTaskSpecProvided"); + + expect( + await contracts.gelatoCore + .connect(address.providerWallet) + .isTaskSpecProvided( + address.providerAddress, + connectGelatoDebtBridgeFromMakerTaskSpec + ) + ).to.be.equal("OK"); + + expect( + await contracts.gelatoCore + .connect(address.providerWallet) + .taskSpecGasPriceCeil( + address.providerAddress, + await contracts.gelatoCore + .connect(address.providerWallet) + .hashTaskSpec(connectGelatoDebtBridgeFromMakerTaskSpec) + ) + ).to.be.equal(gasPriceCeil); + + //#endregion + + //#endregion + return { + address: address, + contracts: contracts, + vaultId: vaultId, + spells: spells, + }; + } + + async getABI() { + return { + PriceOracleResolverABI: PriceOracleResolverABI, + ConnectGelatoABI: ConnectGelato.abi, + ConnectAuthABI: ConnectAuth.abi, + }; + } + + async getConstants() { + return { + MIN_COL_RATIO_MAKER: MIN_COL_RATIO_MAKER, + GAS_PRICE_CEIL: GAS_PRICE_CEIL, + GAS_LIMIT: GAS_LIMIT, + MAKER_INITIAL_DEBT: MAKER_INITIAL_DEBT, + }; + } + + getConnectAuth() { + return ConnectAuth; + } +} + +module.exports = Helper; diff --git a/test/3_ConditionMakerVaultUnsafe.test.js b/test/unit_tests/4_ConditionMakerVaultUnsafe.test.js similarity index 91% rename from test/3_ConditionMakerVaultUnsafe.test.js rename to test/unit_tests/4_ConditionMakerVaultUnsafe.test.js index 3016273..a94d369 100644 --- a/test/3_ConditionMakerVaultUnsafe.test.js +++ b/test/unit_tests/4_ConditionMakerVaultUnsafe.test.js @@ -4,13 +4,13 @@ const {ethers} = hre; // #region Contracts ABI -const ConnectMaker = require("../pre-compiles/ConnectMaker.json"); -const GetCdps = require("../pre-compiles/GetCdps.json"); -const DssCdpManager = require("../pre-compiles/DssCdpManager.json"); -const InstaList = require("../pre-compiles/InstaList.json"); -const InstaAccount = require("../pre-compiles/InstaAccount.json"); -const InstaIndex = require("../pre-compiles/InstaIndex.json"); -const IERC20 = require("../pre-compiles/IERC20.json"); +const ConnectMaker = require("../../pre-compiles/ConnectMaker.json"); +const GetCdps = require("../../pre-compiles/GetCdps.json"); +const DssCdpManager = require("../../pre-compiles/DssCdpManager.json"); +const InstaList = require("../../pre-compiles/InstaList.json"); +const InstaAccount = require("../../pre-compiles/InstaAccount.json"); +const InstaIndex = require("../../pre-compiles/InstaIndex.json"); +const IERC20 = require("../../pre-compiles/IERC20.json"); const ORACLE_MAKER_ETH_USD = "ETH/USD-Maker-v1"; const ORACLE_MAKER_ETH_USD_ADDR = "0x729D19f657BD0614b4985Cf1D82531c67569197B"; diff --git a/test/4_ConnectGelatoProviderPayment.test.js b/test/unit_tests/5_ConnectGelatoProviderPayment.test.js similarity index 92% rename from test/4_ConnectGelatoProviderPayment.test.js rename to test/unit_tests/5_ConnectGelatoProviderPayment.test.js index b244c1f..f17803e 100644 --- a/test/4_ConnectGelatoProviderPayment.test.js +++ b/test/unit_tests/5_ConnectGelatoProviderPayment.test.js @@ -4,15 +4,15 @@ const {ethers} = hre; // #region Contracts ABI -const ConnectMaker = require("../pre-compiles/ConnectMaker.json"); -const GetCdps = require("../pre-compiles/GetCdps.json"); -const DssCdpManager = require("../pre-compiles/DssCdpManager.json"); -const ConnectBasic = require("../pre-compiles/ConnectBasic.json"); -const InstaList = require("../pre-compiles/InstaList.json"); -const InstaAccount = require("../pre-compiles/InstaAccount.json"); -const InstaIndex = require("../pre-compiles/InstaIndex.json"); -const IERC20 = require("../pre-compiles/IERC20.json"); -const InstaConnector = require("../pre-compiles/InstaConnectors.json"); +const ConnectMaker = require("../../pre-compiles/ConnectMaker.json"); +const GetCdps = require("../../pre-compiles/GetCdps.json"); +const DssCdpManager = require("../../pre-compiles/DssCdpManager.json"); +const ConnectBasic = require("../../pre-compiles/ConnectBasic.json"); +const InstaList = require("../../pre-compiles/InstaList.json"); +const InstaAccount = require("../../pre-compiles/InstaAccount.json"); +const InstaIndex = require("../../pre-compiles/InstaIndex.json"); +const IERC20 = require("../../pre-compiles/IERC20.json"); +const InstaConnector = require("../../pre-compiles/InstaConnectors.json"); // #endregion diff --git a/test/6_ConnectGelatoDebtBridge.test.js b/test/unit_tests/6_ConnectGelatoDebtBridge.test.js similarity index 100% rename from test/6_ConnectGelatoDebtBridge.test.js rename to test/unit_tests/6_ConnectGelatoDebtBridge.test.js diff --git a/test/5_PriceOracleResolver.test.js b/test/unit_tests/7_PriceOracleResolver.test.js similarity index 100% rename from test/5_PriceOracleResolver.test.js rename to test/unit_tests/7_PriceOracleResolver.test.js diff --git a/test/7_ProviderModuleDSA.test.js b/test/unit_tests/8_ProviderModuleDSA.test.js similarity index 94% rename from test/7_ProviderModuleDSA.test.js rename to test/unit_tests/8_ProviderModuleDSA.test.js index 1c5dce5..8ccc450 100644 --- a/test/7_ProviderModuleDSA.test.js +++ b/test/unit_tests/8_ProviderModuleDSA.test.js @@ -5,11 +5,11 @@ const GelatoCoreLib = require("@gelatonetwork/core"); // #region Contracts ABI -const ConnectAuth = require("../pre-compiles/ConnectAuth.json"); -const InstaList = require("../pre-compiles/InstaList.json"); -const InstaAccount = require("../pre-compiles/InstaAccount.json"); -const InstaIndex = require("../pre-compiles/InstaIndex.json"); -const InstaConnectors = require("../pre-compiles/InstaConnectors.json"); +const ConnectAuth = require("../../pre-compiles/ConnectAuth.json"); +const InstaList = require("../../pre-compiles/InstaList.json"); +const InstaAccount = require("../../pre-compiles/InstaAccount.json"); +const InstaIndex = require("../../pre-compiles/InstaIndex.json"); +const InstaConnectors = require("../../pre-compiles/InstaConnectors.json"); const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; diff --git a/test_temp/2_Full-Refinance-External-Provider.test.js b/test_temp/2_Full-Refinance-External-Provider.test.js deleted file mode 100644 index 8d46827..0000000 --- a/test_temp/2_Full-Refinance-External-Provider.test.js +++ /dev/null @@ -1,831 +0,0 @@ -const {expect} = require("chai"); -const bre = require("@nomiclabs/buidler"); -const {constants} = require("ethers"); -const {ethers} = bre; -const GelatoCoreLib = require("@gelatonetwork/core"); - -// #region Contracts ABI - -const InstaIndex = require("../pre-compiles/InstaIndex.json"); -const InstaList = require("../pre-compiles/InstaList.json"); -const InstaAccount = require("../pre-compiles/InstaAccount.json"); -const ConnectGelato = require("../pre-compiles/ConnectGelato.json"); -const ConnectMaker = require("../pre-compiles/ConnectMaker.json"); -const ConnectCompound = require("../pre-compiles/ConnectCompound.json"); -const ConnectInstaPool = require("../pre-compiles/ConnectInstaPool.json"); -const ConnectAuth = require("../pre-compiles/ConnectAuth.json"); -const ConnectGelatoDebtBridgeABI = require("../artifacts/ConnectGelatoDebtBridge.json"); -const ConnectGelatoProviderPaymentABI = require("../artifacts/ConnectGelatoProviderPayment.json"); -const InstaConnector = require("../pre-compiles/InstaConnectors.json"); -const DssCdpManager = require("../pre-compiles/DssCdpManager.json"); -const GetCdps = require("../pre-compiles/GetCdps.json"); -const IERC20 = require("../pre-compiles/IERC20.json"); -const CTokenInterface = require("../pre-compiles/CTokenInterface.json"); -const GelatoGasPriceOracle = require("../pre-compiles/GelatoGasPriceOracle.json"); -const CompoundResolver = require("../pre-compiles/InstaCompoundResolver.json"); - -const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; -const GAS_LIMIT = "4000000"; -const GAS_PRICE_CEIL = ethers.utils.parseUnits("1000", "gwei"); - -// #endregion - -describe("Debt Bridge with External Provider", function () { - this.timeout(0); - if (bre.network.name !== "ganache") { - console.error("Test Suite is meant to be run on ganache only"); - process.exit(1); - } - - // Wallet to use for local testing - let userWallet; - let userAddress; - let providerWallet; - let providerAddress; - - // Deployed instances - let connectGelato; - let connectMaker; - let connectInstaPool; - let connectCompound; - let instaIndex; - let instaList; - let dssCdpManager; - let getCdps; - let daiToken; - let gelatoCore; - let cDaiToken; - let cEthToken; - let instaMaster; - let instaConnectors; - let gelatoGasPriceOracle; - let compoundResolver; - - // Contracts to deploy and use for local testing - let conditionMakerVaultIsSafe; - let connectGelatoDebtBridge; - let connectGelatoProviderPayment; - let oracleAggregator; - let dsaProviderModule; - - // Creation during test - let dsa; - let connectedGelatoCore; - - before(async function () { - // Get Test Wallet for local testnet - [userWallet] = await ethers.getSigners(); - userAddress = await userWallet.getAddress(); - - [, providerWallet] = await ethers.getSigners(); - providerAddress = await providerWallet.getAddress(); - - instaMaster = await ethers.provider.getSigner( - bre.network.config.InstaMaster - ); - - // Ganache default accounts prefilled with 100 ETH - expect(await userWallet.getBalance()).to.be.gt( - ethers.utils.parseEther("10") - ); - - // ===== Get Deployed Contract Instance ================== - instaIndex = await ethers.getContractAt( - InstaIndex.abi, - bre.network.config.InstaIndex - ); - instaList = await ethers.getContractAt( - InstaList.abi, - bre.network.config.InstaList - ); - connectGelato = await ethers.getContractAt( - ConnectGelato.abi, - bre.network.config.ConnectGelato - ); - connectMaker = await ethers.getContractAt( - ConnectMaker.abi, - bre.network.config.ConnectMaker - ); - connectInstaPool = await ethers.getContractAt( - ConnectInstaPool.abi, - bre.network.config.ConnectInstaPool - ); - connectCompound = await ethers.getContractAt( - ConnectCompound.abi, - bre.network.config.ConnectCompound - ); - dssCdpManager = await ethers.getContractAt( - DssCdpManager.abi, - bre.network.config.DssCdpManager - ); - getCdps = await ethers.getContractAt( - GetCdps.abi, - bre.network.config.GetCdps - ); - daiToken = await ethers.getContractAt(IERC20.abi, bre.network.config.DAI); - gelatoCore = await ethers.getContractAt( - GelatoCoreLib.GelatoCore.abi, - bre.network.config.GelatoCore - ); - cDaiToken = await ethers.getContractAt( - CTokenInterface.abi, - bre.network.config.CDAI - ); - cEthToken = await ethers.getContractAt( - CTokenInterface.abi, - bre.network.config.CETH - ); - instaConnectors = await ethers.getContractAt( - InstaConnector.abi, - bre.network.config.InstaConnectors - ); - gelatoGasPriceOracle = await ethers.getContractAt( - GelatoGasPriceOracle.abi, - bre.network.config.GelatoGasPriceOracle - ); - compoundResolver = await ethers.getContractAt( - CompoundResolver.abi, - bre.network.config.CompoundResolver - ); - // instaEvent = await ethers.getContractAt( - // InstaEvent.abi, - // bre.network.config.InstaEvent - // ) - - // ===== Deploy Needed Contract ================== - - const OracleAggregator = await ethers.getContractFactory( - "OracleAggregator" - ); - oracleAggregator = await OracleAggregator.deploy(); - await oracleAggregator.deployed(); - - const ConditionMakerVaultIsSafe = await ethers.getContractFactory( - "ConditionMakerVaultIsSafe" - ); - conditionMakerVaultIsSafe = await ConditionMakerVaultIsSafe.deploy( - oracleAggregator.address - ); - await conditionMakerVaultIsSafe.deployed(); - - const connectorLength = await instaConnectors.connectorLength(); - const connectorId = connectorLength.add(1); - - const ConnectGelatoDebtBridge = await ethers.getContractFactory( - "ConnectGelatoDebtBridge" - ); - connectGelatoDebtBridge = await ConnectGelatoDebtBridge.deploy( - connectorId, - oracleAggregator.address - ); - await connectGelatoDebtBridge.deployed(); - - const ConnectGelatoProviderPayment = await ethers.getContractFactory( - "ConnectGelatoProviderPayment" - ); - connectGelatoProviderPayment = await ConnectGelatoProviderPayment.deploy( - connectorId.add(1) - ); - await connectGelatoProviderPayment.deployed(); - - const ProviderModuleDSA = await ethers.getContractFactory( - "ProviderModuleDSA" - ); - dsaProviderModule = await ProviderModuleDSA.deploy( - bre.network.config.InstaIndex, - bre.network.config.GelatoCore, - connectGelatoProviderPayment.address - ); - await dsaProviderModule.deployed(); - - /////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////// After Contracts Deployement : Setup /////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////// - - // Gelato Testing environment setup. - // Step 1 : Add EUR/USD Maker Medianizer in the Oracle Aggregator - // Step 2 : Enable Debt Bridge Connector and Gelato Provider Payment Connector - // Step 3 : Executor Staking on Gelato - // Step 4 : Provider put some fund on gelato for paying future tasks executions - // Step 5 : Provider choose a executor - // Step 6 : Provider will add a module - // Step 7 : Provider should whitelist task - - //#region Step 1 Add EUR/USD Maker Medianizer in the Oracle Aggregator - - // Oracle Aggregator is a price feeder aggregator - // You will be able to query price from multiple source through this aggregator - // For the demo we add the ETH/USD Medianizer to the aggregator - // MakerDAO price oracle are called Medianizer - - await oracleAggregator.addOracle( - "ETH/USD", - "0x729D19f657BD0614b4985Cf1D82531c67569197B" - ); - - //#endregion - - //#region Step 2 Enable Debt Bridge Connector and Gelato Provider Payment Connector - - // Debt Bridge Connector is used during refinancing of debt - // This Connect help the user to split a position in one protocol. - // to 2 protocol in a safe way. Both debt position will be safe. - - // Gelato Provider Payment Connector is used for paying the provider - // for task execution. So when futur task will be executed, through a self financing - // transaction (user will pay during the execution of the task) task will - // be executed. Improvind user experience. - - await userWallet.sendTransaction({ - to: bre.network.config.InstaMaster, - value: ethers.utils.parseEther("0.1"), - }); - await instaConnectors - .connect(instaMaster) - .enable(connectGelatoDebtBridge.address); - await instaConnectors - .connect(instaMaster) - .enable(connectGelatoProviderPayment.address); - - expect( - await instaConnectors.isConnector([connectGelatoDebtBridge.address]) - ).to.be.true; - expect( - await instaConnectors.isConnector([connectGelatoProviderPayment.address]) - ).to.be.true; - - //#endregion - - //#region Step 3 Executor Staking on Gelato - - // For task execution provider will ask a executor to watch the - // blockchain for possible execution autorization given by - // the condition that user choose when submitting the task. - // And if all condition are meet executor will execute the task. - // For safety measure Gelato ask the executor to stake a minimum - // amount. - - connectedGelatoCore = gelatoCore.connect(providerWallet); - gelatoCore = gelatoCore.connect(userWallet); - await connectedGelatoCore.stakeExecutor({ - from: providerAddress, - value: await connectedGelatoCore.minExecutorStake(), - }); - - expect( - await connectedGelatoCore.isExecutorMinStaked(providerAddress) - ).to.be.true; - - //#endregion - - //#region Step 4 Provider put some fund on gelato for paying future tasks executions - - // Provider put some funds in gelato system for paying the - // Executor when this one will execute task on behalf of the - // Provider. At each provider's task execution, some funds (approximatively - // the gas cost value) will be transfered to the Executor stake. - - const TASK_AUTOMATION_FUNDS = await gelatoCore.minExecProviderFunds( - GAS_LIMIT, - GAS_PRICE_CEIL - ); - - await expect( - connectedGelatoCore.provideFunds(providerAddress, { - value: TASK_AUTOMATION_FUNDS, - }) - ).to.emit(gelatoCore, "LogFundsProvided"); - - expect( - await connectedGelatoCore.providerFunds(providerAddress) - ).to.be.equal(TASK_AUTOMATION_FUNDS); - - //#endregion - - //#region Step 5 Provider choose a executor - - // Provider choose a executor who will execute futur task - // for the provider, it will be compensated by the provider. - - await expect( - connectedGelatoCore.providerAssignsExecutor(providerAddress) - ).to.emit(gelatoCore, "LogProviderAssignedExecutor"); - - expect( - await connectedGelatoCore.executorByProvider(providerAddress) - ).to.be.equal(providerAddress); - - //#endregion - - //#region Step 6 Provider will add a module - - // By adding a module the provider will format future task's - // payload by adding some specificity like his address to the - // Payment connector for receiving payment of User. - - await expect( - connectedGelatoCore.addProviderModules([dsaProviderModule.address]) - ).to.emit(gelatoCore, "LogProviderModuleAdded"); - - expect( - await connectedGelatoCore.isModuleProvided( - providerAddress, - dsaProviderModule.address - ) - ).to.be.true; - - //#endregion - - //#region Step 7 Provider should whitelist task - - // By WhiteList task, the provider can constrain the type - // of task the user can submitting. - - //#region Actions - - const spells = []; - - let debtBridge = new GelatoCoreLib.Action({ - addr: connectGelatoDebtBridge.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(debtBridge); - - let flashBorrow = new GelatoCoreLib.Action({ - addr: connectInstaPool.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(flashBorrow); - - let paybackMaker = new GelatoCoreLib.Action({ - addr: connectMaker.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(paybackMaker); - - let withdrawMaker = new GelatoCoreLib.Action({ - addr: connectMaker.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(withdrawMaker); - - let depositCompound = new GelatoCoreLib.Action({ - addr: connectCompound.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(depositCompound); - - let borrowCompound = new GelatoCoreLib.Action({ - addr: connectCompound.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(borrowCompound); - - let flashPayBack = new GelatoCoreLib.Action({ - addr: connectInstaPool.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(flashPayBack); - - let payProvider = new GelatoCoreLib.Action({ - addr: connectGelatoProviderPayment.address, - data: constants.HashZero, - operation: GelatoCoreLib.Operation.Delegatecall, - dataFlow: GelatoCoreLib.DataFlow.None, - termsOkCheck: false, - value: 0, - }); - - spells.push(payProvider); - - const gasPriceCeil = constants.MaxUint256; - - const gelatoFlashLoanTaskSpec = new GelatoCoreLib.TaskSpec({ - conditions: [conditionMakerVaultIsSafe.address], - actions: spells, - gasPriceCeil, - }); - - await expect( - connectedGelatoCore.provideTaskSpecs([gelatoFlashLoanTaskSpec]) - ).to.emit(gelatoCore, "LogTaskSpecProvided"); - - expect( - await connectedGelatoCore.isTaskSpecProvided( - providerAddress, - gelatoFlashLoanTaskSpec - ) - ).to.be.equal("OK"); - - expect( - await connectedGelatoCore.taskSpecGasPriceCeil( - providerAddress, - await connectedGelatoCore.hashTaskSpec(gelatoFlashLoanTaskSpec) - ) - ).to.be.equal(gasPriceCeil); - - //#endregion - - //#endregion - }); - - it("#1: Use Maker Compound refinancing if the maker vault become unsafe after a market move.", async function () { - // User Actions - // Step 1 : User create a DeFi Smart Account - // Step 2 : User open a Vault, put some ether on it and borrow some dai - // Step 3 : User give authorization to gelato to use his DSA on his behalf. - // Step 4 : User submit a Debt Refinancing task if market move against him - // Step 5 : Market Move against the user (Mock) - // Step 6 : Executor execute the user's task - - //#region Step 1 User create a DeFi Smart Account - - // User create a Instadapp DeFi Smart Account - // who give him the possibility to interact - // with a large list of DeFi protocol through one - // Proxy account. - - const dsaAccountCount = await instaList.accounts(); - - await expect(instaIndex.build(userAddress, 1, userAddress)).to.emit( - instaIndex, - "LogAccountCreated" - ); - const dsaID = dsaAccountCount.add(1); - await expect(await instaList.accounts()).to.be.equal(dsaID); - - // Instantiate the DSA - dsa = await ethers.getContractAt( - InstaAccount.abi, - await instaList.accountAddr(dsaID) - ); - - //#endregion - - //#region Step 2 User open a Vault, put some ether on it and borrow some dai - - // User open a maker vault - // He deposit 10 Eth on it - // He borrow a 1000 DAI - - const openVault = await bre.run("abi-encode-withselector", { - abi: ConnectMaker.abi, - functionname: "open", - inputs: ["ETH-A"], - }); - - await dsa.cast([bre.network.config.ConnectMaker], [openVault], userAddress); - - let cdps = await getCdps.getCdpsAsc(dssCdpManager.address, dsa.address); - let cdpId = String(cdps.ids[0]); - - expect(cdps.ids[0].isZero()).to.be.false; - - await dsa.cast( - [bre.network.config.ConnectMaker], - [ - await bre.run("abi-encode-withselector", { - abi: ConnectMaker.abi, - functionname: "deposit", - inputs: [cdpId, ethers.utils.parseEther("10"), 0, 0], - }), - ], - userAddress, - { - value: ethers.utils.parseEther("10"), - } - ); - - let makerVaultInitialBorrow = ethers.utils.parseUnits("1000", 18); - - await dsa.cast( - [bre.network.config.ConnectMaker], - [ - await bre.run("abi-encode-withselector", { - abi: ConnectMaker.abi, - functionname: "borrow", - inputs: [cdpId, makerVaultInitialBorrow, 0, 0], - }), - ], - userAddress - ); - - expect(await daiToken.balanceOf(dsa.address)).to.be.equal( - ethers.utils.parseEther("1000") - ); - - //#endregion - - //#region Step 3 User give authorization to gelato to use his DSA on his behalf. - - // Instadapp DSA contract give the possibility to the user to delegate - // action by giving authorization. - // In this case user give authorization to gelato to execute - // task for him if needed. - - await dsa.cast( - [bre.network.config.ConnectAuth], - [ - await bre.run("abi-encode-withselector", { - abi: ConnectAuth.abi, - functionname: "add", - inputs: [gelatoCore.address], - }), - ], - userAddress - ); - - expect(await dsa.isAuth(gelatoCore.address)).to.be.true; - - //#endregion - - //#region Step 4 User submit a Debt Refinancing task if market move against him - - // User submit the refinancing task if market move against him. - // So in this case if the maker vault go to the unsafe area - // the refinancing task will be executed and the position - // will be split on two position on maker and compound. - // It will be done through a algorithm that will optimize the - // total borrow rate. - - const debtBridgeCondition = new GelatoCoreLib.Condition({ - inst: conditionMakerVaultIsSafe.address, - data: await conditionMakerVaultIsSafe.getConditionData( - cdpId, - "ETH/USD", - ethers.utils.parseUnits("40", 17) - ), - }); - - // ======= Action/Spells setup ====== - const spells = []; - - let debtBridgeCalculation = new GelatoCoreLib.Action({ - addr: connectGelatoDebtBridge.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectGelatoDebtBridgeABI.abi, - functionname: "fullRefinanceMakerToCompound", - inputs: [cdpId, 0, 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(debtBridgeCalculation); - - let flashBorrow = new GelatoCoreLib.Action({ - addr: connectInstaPool.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectInstaPool.abi, - functionname: "flashBorrow", - inputs: [bre.network.config.DAI, 0, "100", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(flashBorrow); - - let paybackMaker = new GelatoCoreLib.Action({ - addr: connectMaker.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectMaker.abi, - functionname: "payback", - inputs: [cdpId, 0, "101", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(paybackMaker); - - let withdrawMaker = new GelatoCoreLib.Action({ - addr: connectMaker.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectMaker.abi, - functionname: "withdraw", - inputs: [cdpId, 0, "102", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(withdrawMaker); - - let depositCompound = new GelatoCoreLib.Action({ - addr: connectCompound.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectCompound.abi, - functionname: "deposit", - inputs: [ETH, 0, "103", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(depositCompound); - - let borrowCompound = new GelatoCoreLib.Action({ - addr: connectCompound.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectCompound.abi, - functionname: "borrow", - inputs: [bre.network.config.DAI, 0, "104", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(borrowCompound); - - let flashPayBack = new GelatoCoreLib.Action({ - addr: connectInstaPool.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectInstaPool.abi, - functionname: "flashPayback", - inputs: [bre.network.config.DAI, 0, 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(flashPayBack); - - let payProvider = new GelatoCoreLib.Action({ - addr: connectGelatoProviderPayment.address, - data: await bre.run("abi-encode-withselector", { - abi: ConnectGelatoProviderPaymentABI.abi, - functionname: "payProvider", - inputs: [ethers.constants.AddressZero, ETH, 0, "105", 0], - }), - operation: GelatoCoreLib.Operation.Delegatecall, - }); - - spells.push(payProvider); - - const refinanceIfCompoundBorrowIsBetter = new GelatoCoreLib.Task({ - conditions: [debtBridgeCondition], - actions: spells, - }); - - const gelatoExternalProvider = new GelatoCoreLib.GelatoProvider({ - addr: providerAddress, - module: dsaProviderModule.address, - }); - - const expiryDate = 0; - await expect( - dsa.cast( - [connectGelato.address], // targets - [ - await bre.run("abi-encode-withselector", { - abi: ConnectGelato.abi, - functionname: "submitTask", - inputs: [ - gelatoExternalProvider, - refinanceIfCompoundBorrowIsBetter, - expiryDate, - ], - }), - ], // datas - userAddress, // origin - { - gasLimit: 5000000, - } - ) - ).to.emit(gelatoCore, "LogTaskSubmitted"); - - const taskReceipt = new GelatoCoreLib.TaskReceipt({ - id: await gelatoCore.currentTaskReceiptId(), - userProxy: dsa.address, - provider: gelatoExternalProvider, - tasks: [refinanceIfCompoundBorrowIsBetter], - expiryDate, - }); - - //#endregion - - //#region Step 5 Market Move against the user (Mock) - - // Ether market price went from the current price to 250$ - - const gelatoGasPrice = await bre.run("fetchGelatoGasPrice"); - expect(gelatoGasPrice).to.be.lte(GAS_PRICE_CEIL); - - await oracleAggregator.mock(true, ethers.utils.parseUnits("400", 18)); - - expect( - await connectedGelatoCore.canExec(taskReceipt, GAS_LIMIT, gelatoGasPrice) - ).to.be.equal("ConditionNotOk:NotOKMakerVaultIsSafe"); - - await oracleAggregator.mock(true, ethers.utils.parseUnits("380", 18)); - - expect( - await connectedGelatoCore.canExec(taskReceipt, GAS_LIMIT, gelatoGasPrice) - ).to.be.equal("OK"); - - //#endregion - - //#region Step 6 Executor execute the user's task - - // The market move make the vault unsafe, so the executor - // will execute the user's task to make the user position safe - // by a debt refinancing in compound. - - //#region EXPECTED OUTCOME - - let fees = ethers.utils - .parseUnits(String(1933090 + 19331 * 2), 0) - .mul(await gelatoGasPriceOracle.latestAnswer()); - let debt = await connectGelatoDebtBridge.getMakerVaultDebt(cdpId); - let collateral = ( - await connectGelatoDebtBridge.getMakerVaultCollateralBalance(cdpId) - ).sub(fees); - - //#endregion - - let providerBalanceBeforeExecution = await providerWallet.getBalance(); - - await expect( - connectedGelatoCore.exec(taskReceipt, { - gasPrice: gelatoGasPrice, // Exectutor must use gelatoGasPrice (Chainlink fast gwei) - gasLimit: GAS_LIMIT, - }) - ).to.emit(gelatoCore, "LogExecSuccess"); - - let providerBalanceAfterExecution = await providerWallet.getBalance(); - - expect(providerBalanceAfterExecution).to.be.gt( - providerBalanceBeforeExecution - ); - - // compound position of DSA on cDai and cEth - let compoundPosition = await compoundResolver.getCompoundData(dsa.address, [ - cDaiToken.address, - cEthToken.address, - ]); - - // https://compound.finance/docs/ctokens#exchange-rate - // calculate cEth/ETH rate to convert back cEth to ETH - // for comparing with the withdrew Ether to the deposited one. - let exchangeRateCethToEth = (await cEthToken.getCash()) - .add(await cEthToken.totalBorrows()) - .sub(await cEthToken.totalReserves()) - .div(await cEthToken.totalSupply()); - - // Estimated amount to borrowed token should be equal to the actual one read on compound contracts - expect(debt).to.be.equal(compoundPosition[0].borrowBalanceStoredUser); - - // Estimated amount of collateral should be equal to the actual one read on compound contracts - expect( - collateral.sub( - compoundPosition[1].balanceOfUser.mul(exchangeRateCethToEth) - ) - ).to.be.lt(ethers.utils.parseUnits("1", 12)); - - debt = await connectGelatoDebtBridge.getMakerVaultDebt(cdpId); - collateral = await connectGelatoDebtBridge.getMakerVaultCollateralBalance( - cdpId - ); // in Ether. - - expect(debt).to.be.equal(ethers.constants.Zero); - expect(collateral).to.be.equal(ethers.constants.Zero); - - // DSA contain 1000 DAI - expect(await daiToken.balanceOf(dsa.address)).to.be.equal( - makerVaultInitialBorrow - ); - - //#endregion - }); -}); diff --git a/test/3_Partial-Refinance-External-Provider.test.js b/test_temp/3_Partial-Refinance-External-Provider.test.js similarity index 100% rename from test/3_Partial-Refinance-External-Provider.test.js rename to test_temp/3_Partial-Refinance-External-Provider.test.js