From 7803bc5102e6ea5396d37f201a62b15dcb6c848d Mon Sep 17 00:00:00 2001 From: Shivva Date: Mon, 23 Nov 2020 08:17:25 +0100 Subject: [PATCH] Add is destination vault will be safe condition --- contracts/constants/CInstaDapp.sol | 3 + .../ConditionIsDestVaultWillBeSafe.sol | 77 +++++ .../contracts/resolvers/MakerResolver.sol | 20 +- contracts/functions/dapps/FMaker.sol | 70 +++- contracts/interfaces/InstaDapp/IInstaDapp.sol | 4 + .../dapps/Maker/ITokenJoinInterface.sol | 7 + contracts/vendor/Convert.sol | 19 ++ .../ConditionIsDestVaultWillBeSafe.deploy.js | 27 ++ hardhat.config.js | 4 +- ...ETHA-ETHB-WITH-Vault-Creation.mock.test.js | 13 + ...ells-ETHA-ETHB-With-Vault-Creation.mock.js | 1 + ...MakerToMakerWithVaultBCreationMock.mock.js | 6 - test/helpers/services/getContracts.js | 5 + .../1_ETHA-ETHB-With-Vault-Creation.test.js | 10 + .../from_maker/full/2_ETHA-ETHB.test.js | 12 + ...getSpells-ETHA-ETHB-With-Vault-Creation.js | 1 + .../helpers/services/getSpells-ETHA-ETHB.js | 1 + .../helpers/setupFullRefinanceMakerToMaker.js | 6 - ...RefinanceMakerToMakerWithVaultBCreation.js | 6 - .../1_ConditionDebtBridgeIsAffordable.test.js | 2 +- .../2_ConditionIsDestVaultWillBeSafe.test.js | 306 ++++++++++++++++++ .../functions/0_FGelatoDebtBridge.test.js | 4 +- .../config/mainnet-deployments.js | 0 23 files changed, 578 insertions(+), 26 deletions(-) create mode 100644 contracts/contracts/gelato/conditions/ConditionIsDestVaultWillBeSafe.sol create mode 100644 contracts/interfaces/dapps/Maker/ITokenJoinInterface.sol create mode 100644 contracts/vendor/Convert.sol create mode 100644 deploy/gelato/conditions/ConditionIsDestVaultWillBeSafe.deploy.js create mode 100644 test/unit/conditions/2_ConditionIsDestVaultWillBeSafe.test.js rename {hardhat => tool}/config/mainnet-deployments.js (100%) diff --git a/contracts/constants/CInstaDapp.sol b/contracts/constants/CInstaDapp.sol index cccf10c..fe31eb2 100644 --- a/contracts/constants/CInstaDapp.sol +++ b/contracts/constants/CInstaDapp.sol @@ -16,3 +16,6 @@ address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Insta Pool address constant INSTA_POOL_RESOLVER = 0xa004a5afBa04b74037E9E52bA1f7eb02b5E61509; uint256 constant ROUTE_1_TOLERANCE = 1005e15; + +// Insta Mapping +address constant INSTA_MAPPING = 0xe81F70Cc7C0D46e12d70efc60607F16bbD617E88; diff --git a/contracts/contracts/gelato/conditions/ConditionIsDestVaultWillBeSafe.sol b/contracts/contracts/gelato/conditions/ConditionIsDestVaultWillBeSafe.sol new file mode 100644 index 0000000..30aa6a7 --- /dev/null +++ b/contracts/contracts/gelato/conditions/ConditionIsDestVaultWillBeSafe.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.7.4; +pragma experimental ABIEncoderV2; + +import { + GelatoConditionsStandard +} from "@gelatonetwork/core/contracts/conditions/GelatoConditionsStandard.sol"; +import { + _getMakerVaultDebt, + _getMakerVaultCollateralBalance, + _isVaultWillBeSafe, + _isNewVaultWillBeSafe +} from "../../../functions/dapps/FMaker.sol"; +import { + _getRealisedDebt +} from "../../../functions/gelato/FGelatoDebtBridge.sol"; + +import {GelatoBytes} from "../../../lib/GelatoBytes.sol"; +import "hardhat/console.sol"; + +contract ConditionIsDestVaultWillBeSafe is GelatoConditionsStandard { + using GelatoBytes for bytes; + + function getConditionData( + uint256 _fromVaultId, + uint256 _destVaultId, + string memory _destColType + ) public pure virtual returns (bytes memory) { + return abi.encode(_fromVaultId, _destVaultId, _destColType); + } + + function ok( + uint256, + bytes calldata _conditionData, + uint256 + ) public view virtual override returns (string memory) { + ( + uint256 _fromVaultId, + uint256 _destVaultId, + string memory _destColType + ) = abi.decode(_conditionData, (uint256, uint256, string)); + + return destVaultWillBeSafe(_fromVaultId, _destVaultId, _destColType); + } + + function destVaultWillBeSafe( + uint256 _fromVaultId, + uint256 _destVaultId, + string memory _destColType + ) public view returns (string memory) { + uint256 wDaiToBorrow = + _getRealisedDebt(_getMakerVaultDebt(_fromVaultId)); + uint256 wColToDeposit = _getMakerVaultCollateralBalance(_fromVaultId); + + return + isDestVaultWillBeSafe( + _destVaultId, + wDaiToBorrow, + wColToDeposit, + _destColType + ) + ? OK + : "DestVaultWillNotBeSafe"; + } + + function isDestVaultWillBeSafe( + uint256 _vaultId, + uint256 _wDaiToBorrow, + uint256 _wColToDeposit, + string memory _colType + ) public view returns (bool) { + return + _vaultId == 0 + ? _isNewVaultWillBeSafe(_colType, _wDaiToBorrow, _wColToDeposit) + : _isVaultWillBeSafe(_vaultId, _wDaiToBorrow, _wColToDeposit); + } +} diff --git a/contracts/contracts/resolvers/MakerResolver.sol b/contracts/contracts/resolvers/MakerResolver.sol index db6bcc0..6dd07f1 100644 --- a/contracts/contracts/resolvers/MakerResolver.sol +++ b/contracts/contracts/resolvers/MakerResolver.sol @@ -4,7 +4,9 @@ pragma solidity 0.7.4; import { _getMakerRawVaultDebt, _getMakerVaultDebt, - _getMakerVaultCollateralBalance + _getMakerVaultCollateralBalance, + _isVaultWillBeSafe, + _isNewVaultWillBeSafe } from "../../functions/dapps/FMaker.sol"; contract MakerResolver { @@ -29,4 +31,20 @@ contract MakerResolver { { return _getMakerVaultCollateralBalance(_vaultId); } + + function isVaultWillBeSafe( + uint256 _vaultId, + uint256 _amtToBorrow, + uint256 _colToDeposit + ) public view returns (bool) { + return _isVaultWillBeSafe(_vaultId, _amtToBorrow, _colToDeposit); + } + + function isNewVaultWillBeSafe( + string memory _colType, + uint256 _amtToBorrow, + uint256 _colToDeposit + ) public view returns (bool) { + return _isNewVaultWillBeSafe(_colType, _amtToBorrow, _colToDeposit); + } } diff --git a/contracts/functions/dapps/FMaker.sol b/contracts/functions/dapps/FMaker.sol index 9688896..f3ce747 100644 --- a/contracts/functions/dapps/FMaker.sol +++ b/contracts/functions/dapps/FMaker.sol @@ -2,9 +2,15 @@ pragma solidity 0.7.4; import {MCD_MANAGER} from "../../constants/CMaker.sol"; +import {INSTA_MAPPING} from "../../constants/CInstaDapp.sol"; +import { + ITokenJoinInterface +} from "../../interfaces/dapps/Maker/ITokenJoinInterface.sol"; import {IMcdManager} from "../../interfaces/dapps/Maker/IMcdManager.sol"; +import {InstaMapping} from "../../interfaces/InstaDapp/IInstaDapp.sol"; import {IVat} from "../../interfaces/dapps/Maker/IVat.sol"; -import {RAY, sub, mul} from "../../vendor/DSMath.sol"; +import {RAY, add, sub, mul} from "../../vendor/DSMath.sol"; +import {_stringToBytes32, _convertTo18} from "../../vendor/Convert.sol"; function _getMakerVaultDebt(uint256 _vaultId) view returns (uint256 wad) { IMcdManager manager = IMcdManager(MCD_MANAGER); @@ -48,6 +54,59 @@ function _getMakerVaultCollateralBalance(uint256 _vaultId) return ink; } +function _isVaultWillBeSafe( + uint256 _vaultId, + uint256 _amtToBorrow, + uint256 _colToDeposit +) view returns (bool) { + require(_vaultId != 0, "_isVaultWillBeSafe: invalid vault id."); + + IMcdManager manager = IMcdManager(MCD_MANAGER); + + (bytes32 ilk, address urn) = _getVaultData(manager, _vaultId); + + ITokenJoinInterface tokenJoinContract = + ITokenJoinInterface(InstaMapping(INSTA_MAPPING).gemJoinMapping(ilk)); + + IVat vat = IVat(manager.vat()); + (, uint256 rate, uint256 spot, , ) = vat.ilks(ilk); + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + uint256 dai = vat.dai(urn); + + uint256 dink = _convertTo18(tokenJoinContract.dec(), _colToDeposit); + uint256 dart = _getBorrowAmt(_amtToBorrow, dai, rate); + + ink = add(ink, dink); + art = add(art, dart); + + uint256 tab = mul(rate, art); + + return tab <= mul(ink, spot); +} + +function _isNewVaultWillBeSafe( + string memory _colType, + uint256 _amtToBorrow, + uint256 _colToDeposit +) view returns (bool) { + IMcdManager manager = IMcdManager(MCD_MANAGER); + IVat vat = IVat(manager.vat()); + + bytes32 ilk = _stringToBytes32(_colType); + + (, uint256 rate, uint256 spot, , ) = vat.ilks(ilk); + + ITokenJoinInterface tokenJoinContract = + ITokenJoinInterface(InstaMapping(INSTA_MAPPING).gemJoinMapping(ilk)); + + uint256 ink = _convertTo18(tokenJoinContract.dec(), _colToDeposit); + uint256 art = _getBorrowAmt(_amtToBorrow, 0, rate); + + uint256 tab = mul(rate, art); + + return tab <= mul(ink, spot); +} + function _getVaultData(IMcdManager manager, uint256 vault) view returns (bytes32 ilk, address urn) @@ -55,3 +114,12 @@ function _getVaultData(IMcdManager manager, uint256 vault) ilk = manager.ilks(vault); urn = manager.urns(vault); } + +function _getBorrowAmt( + uint256 _amt, + uint256 _dai, + uint256 _rate +) pure returns (uint256 dart) { + dart = sub(mul(_amt, RAY), _dai) / _rate; + dart = mul(dart, _rate) < mul(_amt, RAY) ? dart + 1 : dart; +} diff --git a/contracts/interfaces/InstaDapp/IInstaDapp.sol b/contracts/interfaces/InstaDapp/IInstaDapp.sol index d8c874d..9ff4bbe 100644 --- a/contracts/interfaces/InstaDapp/IInstaDapp.sol +++ b/contracts/interfaces/InstaDapp/IInstaDapp.sol @@ -41,3 +41,7 @@ interface ConnectorInterface { function name() external view returns (string memory); } + +interface InstaMapping { + function gemJoinMapping(bytes32) external view returns (address); +} diff --git a/contracts/interfaces/dapps/Maker/ITokenJoinInterface.sol b/contracts/interfaces/dapps/Maker/ITokenJoinInterface.sol new file mode 100644 index 0000000..f51c661 --- /dev/null +++ b/contracts/interfaces/dapps/Maker/ITokenJoinInterface.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.7.4; +pragma experimental ABIEncoderV2; + +interface ITokenJoinInterface { + function dec() external view returns (uint256); +} diff --git a/contracts/vendor/Convert.sol b/contracts/vendor/Convert.sol new file mode 100644 index 0000000..3115376 --- /dev/null +++ b/contracts/vendor/Convert.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.7.4; + +import {RAY, add, sub, mul} from "./DSMath.sol"; + + function _stringToBytes32(string memory str) + pure + returns (bytes32 result) + { + require(bytes(str).length != 0, "string-empty"); + assembly { + result := mload(add(str, 32)) + } + } + + function _convertTo18(uint256 _dec, uint256 _amt) + pure returns (uint256 amt) { + amt = mul(_amt, 10**(18 - _dec)); + } \ No newline at end of file diff --git a/deploy/gelato/conditions/ConditionIsDestVaultWillBeSafe.deploy.js b/deploy/gelato/conditions/ConditionIsDestVaultWillBeSafe.deploy.js new file mode 100644 index 0000000..96cd639 --- /dev/null +++ b/deploy/gelato/conditions/ConditionIsDestVaultWillBeSafe.deploy.js @@ -0,0 +1,27 @@ +const { sleep } = require("@gelatonetwork/core"); + +module.exports = async (hre) => { + if (hre.network.name === "mainnet") { + console.log( + "Deploying ConditionIsDestVaultWillBeSafe to mainnet. Hit ctrl + c to abort" + ); + await sleep(10000); + } + + const { deployments } = hre; + const { deploy } = deployments; + const { deployer } = await hre.getNamedAccounts(); + + // the following will only deploy "ConditionIsDestVaultWillBeSafe" + // if the contract was never deployed or if the code changed since last deployment + await deploy("ConditionIsDestVaultWillBeSafe", { + from: deployer, + gasPrice: hre.network.config.gasPrice, + log: hre.network.name === "mainnet" ? true : false, + }); +}; + +module.exports.skip = async (hre) => { + return hre.network.name === "mainnet" ? true : false; +}; +module.exports.tags = ["ConditionIsDestVaultWillBeSafe"]; diff --git a/hardhat.config.js b/hardhat.config.js index ee90381..3105810 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -12,7 +12,7 @@ const { utils } = require("ethers"); const GelatoCoreLib = require("@gelatonetwork/core"); -const mainnetDeployments = require("./hardhat/config/mainnet-deployments"); +const mainnetDeployments = require("./tool/config/mainnet-deployments"); // Process Env Variables require("dotenv").config(); @@ -55,7 +55,7 @@ module.exports = { // timeout: 150000, forking: { url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`, - blockNumber: 11189230, + blockNumber: 11310523, }, // Custom ...mainnetDeployments, diff --git a/test/gas/debt_bridge/full/from_maker/1_ETHA-ETHB-WITH-Vault-Creation.mock.test.js b/test/gas/debt_bridge/full/from_maker/1_ETHA-ETHB-WITH-Vault-Creation.mock.test.js index 493c328..d48febf 100644 --- a/test/gas/debt_bridge/full/from_maker/1_ETHA-ETHB-WITH-Vault-Creation.mock.test.js +++ b/test/gas/debt_bridge/full/from_maker/1_ETHA-ETHB-WITH-Vault-Creation.mock.test.js @@ -20,9 +20,11 @@ describe("Gas Measurements: Full Debt Bridge From Maker ETH-A to ETH-B", functio // Payload Params for ConnectGelatoFullDebtBridgeFromMaker and ConditionMakerVaultUnsafe let vaultAId; + let vaultBId; let conditionMakerVaultUnsafeObj; let conditionDebtBridgeIsAffordableObj; + let conditionIsDestVaultWillBeSafe; // For TaskSpec and for Task let gelatoDebtBridgeSpells = []; @@ -52,6 +54,7 @@ describe("Gas Measurements: Full Debt Bridge From Maker ETH-A to ETH-B", functio wallets = result.wallets; contracts = result.contracts; vaultAId = result.vaultAId; + vaultBId = result.vaultBId; gelatoDebtBridgeSpells = result.spells; ABI = result.ABI; @@ -81,10 +84,20 @@ describe("Gas Measurements: Full Debt Bridge From Maker ETH-A to ETH-B", functio ), }); + conditionIsDestVaultWillBeSafe = new GelatoCoreLib.Condition({ + inst: contracts.conditionIsDestVaultWillBeSafe.address, + data: await contracts.conditionIsDestVaultWillBeSafe.getConditionData( + vaultAId, + vaultBId, + "ETH-B" + ), + }); + refinanceFromEthAToBIfVaultUnsafe = new GelatoCoreLib.Task({ conditions: [ conditionMakerVaultUnsafeObj, conditionDebtBridgeIsAffordableObj, + conditionIsDestVaultWillBeSafe, ], actions: gelatoDebtBridgeSpells, }); diff --git a/test/gas/debt_bridge/full/from_maker/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.mock.js b/test/gas/debt_bridge/full/from_maker/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.mock.js index a79ba8e..f107fa4 100644 --- a/test/gas/debt_bridge/full/from_maker/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.mock.js +++ b/test/gas/debt_bridge/full/from_maker/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.mock.js @@ -41,6 +41,7 @@ module.exports = async function ( conditions: [ contracts.conditionMakerVaultUnsafe.address, contracts.conditionDebtBridgeIsAffordable.address, + contracts.conditionIsDestVaultWillBeSafe.address, ], actions: spells, gasPriceCeil, diff --git a/test/gas/debt_bridge/full/from_maker/helpers/setupFullRefinanceMakerToMakerWithVaultBCreationMock.mock.js b/test/gas/debt_bridge/full/from_maker/helpers/setupFullRefinanceMakerToMakerWithVaultBCreationMock.mock.js index 88fff58..68dba9a 100644 --- a/test/gas/debt_bridge/full/from_maker/helpers/setupFullRefinanceMakerToMakerWithVaultBCreationMock.mock.js +++ b/test/gas/debt_bridge/full/from_maker/helpers/setupFullRefinanceMakerToMakerWithVaultBCreationMock.mock.js @@ -5,7 +5,6 @@ const provideFunds = require("../../../../../helpers/services/gelato/provideFund const providerAssignsExecutor = require("../../../../../helpers/services/gelato/providerAssignsExecutor"); const addProviderModuleDSA = require("../../../../../helpers/services/gelato/addProviderModuleDSA"); const createDSA = require("../../../../../helpers/services/InstaDapp/createDSA"); -const addETHBGemJoinMapping = require("../../../../../helpers/services/maker/addETHBGemJoinMapping"); const initializeMakerCdp = require("../../../../../helpers/services/maker/initializeMakerCdp"); const createVaultForETHB = require("../../../../../helpers/services/maker/createVaultForETHB"); const mockGetSpellsETHAETHBWithVaultCreation = require("./services/getSpells-ETHA-ETHB-With-Vault-Creation.mock"); @@ -38,11 +37,6 @@ module.exports = async function (mockRoute) { contracts.instaIndex, contracts.instaList ); - await addETHBGemJoinMapping( - wallets.userWallet, - contracts.instaMapping, - contracts.instaMaster - ); const vaultAId = await initializeMakerCdp( wallets.userAddress, contracts.DAI, diff --git a/test/helpers/services/getContracts.js b/test/helpers/services/getContracts.js index e698668..644b737 100644 --- a/test/helpers/services/getContracts.js +++ b/test/helpers/services/getContracts.js @@ -113,6 +113,10 @@ module.exports = async function () { "MockConnectGelatoDataFullRefinanceMaker" ); + const conditionIsDestVaultWillBeSafe = await ethers.getContract( + "ConditionIsDestVaultWillBeSafe" + ); + return { connectGelato, connectMaker, @@ -141,5 +145,6 @@ module.exports = async function () { conditionDebtBridgeIsAffordable, mockDebtBridgeETHBExecutor, mockConnectGelatoDataFullRefinanceMaker, + conditionIsDestVaultWillBeSafe, }; }; diff --git a/test/integration/debt_bridge/from_maker/full/1_ETHA-ETHB-With-Vault-Creation.test.js b/test/integration/debt_bridge/from_maker/full/1_ETHA-ETHB-With-Vault-Creation.test.js index ac79c87..cf48a11 100644 --- a/test/integration/debt_bridge/from_maker/full/1_ETHA-ETHB-With-Vault-Creation.test.js +++ b/test/integration/debt_bridge/from_maker/full/1_ETHA-ETHB-With-Vault-Creation.test.js @@ -102,11 +102,21 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B with vault creat ), }); + const conditionIsDestVaultWillBeSafe = new GelatoCoreLib.Condition({ + inst: contracts.conditionIsDestVaultWillBeSafe.address, + data: await contracts.conditionIsDestVaultWillBeSafe.getConditionData( + vaultAId, + 0, + "ETH-B" + ), + }); + // ======= GELATO TASK SETUP ====== const refinanceFromEthAToBIfVaultUnsafe = new GelatoCoreLib.Task({ conditions: [ conditionMakerVaultUnsafeObj, conditionDebtBridgeIsAffordableObj, + conditionIsDestVaultWillBeSafe, ], actions: gelatoDebtBridgeSpells, }); diff --git a/test/integration/debt_bridge/from_maker/full/2_ETHA-ETHB.test.js b/test/integration/debt_bridge/from_maker/full/2_ETHA-ETHB.test.js index 7d9baca..bb34b0e 100644 --- a/test/integration/debt_bridge/from_maker/full/2_ETHA-ETHB.test.js +++ b/test/integration/debt_bridge/from_maker/full/2_ETHA-ETHB.test.js @@ -23,6 +23,7 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () { // Payload Params for ConnectGelatoFullDebtBridgeFromMaker and ConditionMakerVaultUnsafe let vaultAId; + let vaultBId; // For TaskSpec and for Task let gelatoDebtBridgeSpells = []; @@ -39,6 +40,7 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () { wallets = result.wallets; contracts = result.contracts; vaultAId = result.vaultAId; + vaultBId = result.vaultBId; gelatoDebtBridgeSpells = result.spells; ABI = result.ABI; @@ -102,11 +104,21 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () { ), }); + const conditionIsDestVaultWillBeSafe = new GelatoCoreLib.Condition({ + inst: contracts.conditionIsDestVaultWillBeSafe.address, + data: await contracts.conditionIsDestVaultWillBeSafe.getConditionData( + vaultAId, + vaultBId, + "ETH-B" + ), + }); + // ======= GELATO TASK SETUP ====== const refinanceFromEthAToBIfVaultUnsafe = new GelatoCoreLib.Task({ conditions: [ conditionMakerVaultUnsafeObj, conditionDebtBridgeIsAffordableObj, + conditionIsDestVaultWillBeSafe, ], actions: gelatoDebtBridgeSpells, }); diff --git a/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.js b/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.js index fdd46f7..a0698aa 100644 --- a/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.js +++ b/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB-With-Vault-Creation.js @@ -35,6 +35,7 @@ module.exports = async function (wallets, contracts, constants, vaultId) { conditions: [ contracts.conditionMakerVaultUnsafe.address, contracts.conditionDebtBridgeIsAffordable.address, + contracts.conditionIsDestVaultWillBeSafe.address, ], actions: spells, gasPriceCeil, diff --git a/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB.js b/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB.js index 6bb1d07..d1d5365 100644 --- a/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB.js +++ b/test/integration/debt_bridge/from_maker/full/helpers/services/getSpells-ETHA-ETHB.js @@ -41,6 +41,7 @@ module.exports = async function ( conditions: [ contracts.conditionMakerVaultUnsafe.address, contracts.conditionDebtBridgeIsAffordable.address, + contracts.conditionIsDestVaultWillBeSafe.address, ], actions: spells, gasPriceCeil, diff --git a/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMaker.js b/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMaker.js index 54e2d03..4cd9d58 100644 --- a/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMaker.js +++ b/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMaker.js @@ -6,7 +6,6 @@ const provideFunds = require("../../../../../helpers/services/gelato/provideFund const providerAssignsExecutor = require("../../../../../helpers/services/gelato/providerAssignsExecutor"); const addProviderModuleDSA = require("../../../../../helpers/services/gelato/addProviderModuleDSA"); const createDSA = require("../../../../../helpers/services/InstaDapp/createDSA"); -const addETHBGemJoinMapping = require("../../../../../helpers/services/maker/addETHBGemJoinMapping"); const initializeMakerCdp = require("../../../../../helpers/services/maker/initializeMakerCdp"); const createVaultForETHB = require("../../../../../helpers/services/maker/createVaultForETHB"); const getSpellsEthAEthB = require("./services/getSpells-ETHA-ETHB"); @@ -40,11 +39,6 @@ module.exports = async function () { contracts.instaIndex, contracts.instaList ); - await addETHBGemJoinMapping( - wallets.userWallet, - contracts.instaMapping, - contracts.instaMaster - ); const vaultAId = await initializeMakerCdp( wallets.userAddress, contracts.DAI, diff --git a/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMakerWithVaultBCreation.js b/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMakerWithVaultBCreation.js index 6c44081..9f0e0c9 100644 --- a/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMakerWithVaultBCreation.js +++ b/test/integration/debt_bridge/from_maker/full/helpers/setupFullRefinanceMakerToMakerWithVaultBCreation.js @@ -6,7 +6,6 @@ const provideFunds = require("../../../../../helpers/services/gelato/provideFund const providerAssignsExecutor = require("../../../../../helpers/services/gelato/providerAssignsExecutor"); const addProviderModuleDSA = require("../../../../../helpers/services/gelato/addProviderModuleDSA"); const createDSA = require("../../../../../helpers/services/InstaDapp/createDSA"); -const addETHBGemJoinMapping = require("../../../../../helpers/services/maker/addETHBGemJoinMapping"); const initializeMakerCdp = require("../../../../../helpers/services/maker/initializeMakerCdp"); const getSpellsEthAEthBWithVaultCreation = require("./services/getSpells-ETHA-ETHB-With-Vault-Creation"); const getABI = require("../../../../../helpers/services/getABI"); @@ -39,11 +38,6 @@ module.exports = async function () { contracts.instaIndex, contracts.instaList ); - await addETHBGemJoinMapping( - wallets.userWallet, - contracts.instaMapping, - contracts.instaMaster - ); const vaultAId = await initializeMakerCdp( wallets.userAddress, contracts.DAI, diff --git a/test/unit/conditions/1_ConditionDebtBridgeIsAffordable.test.js b/test/unit/conditions/1_ConditionDebtBridgeIsAffordable.test.js index f7ca53f..443b075 100644 --- a/test/unit/conditions/1_ConditionDebtBridgeIsAffordable.test.js +++ b/test/unit/conditions/1_ConditionDebtBridgeIsAffordable.test.js @@ -147,7 +147,7 @@ describe("ConditionDebtBridgeIsAffordable Unit Test", function () { it("#2: ok should return OK when the gas fees not exceed a define amount", async function () { const conditionData = await conditionDebtBridgeIsAffordable.getConditionData( cdpId, - ethers.utils.parseUnits("10", 15) + ethers.utils.parseUnits("10", 16) ); expect( diff --git a/test/unit/conditions/2_ConditionIsDestVaultWillBeSafe.test.js b/test/unit/conditions/2_ConditionIsDestVaultWillBeSafe.test.js new file mode 100644 index 0000000..0fcb786 --- /dev/null +++ b/test/unit/conditions/2_ConditionIsDestVaultWillBeSafe.test.js @@ -0,0 +1,306 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { deployments, 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 Vat = require("../../../artifacts/contracts/interfaces/dapps/Maker/IVat.sol/IVat.json"); +const IERC20 = require("../../../pre-compiles/IERC20.json"); + +// #endregion + +describe("ConditionIsDestVaultWillBeSafe Unit Test", 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 userWallet; + let userAddress; + + let getCdps; + let dssCdpManager; + let instaList; + let instaIndex; + let DAI; + let vat; + + let conditionIsDestVaultWillBeSafe; + + let dsa; + let cdpId; + let ilk; + let ethBIlk; + let amountToBorrow; + + beforeEach(async function () { + // Deploy contract dependencies + await deployments.fixture(); + + // Get Test Wallet for local testnet + [userWallet] = await ethers.getSigners(); + userAddress = await userWallet.getAddress(); + + // Hardhat default accounts prefilled with 100 ETH + expect(await userWallet.getBalance()).to.be.gt( + ethers.utils.parseEther("10") + ); + + instaIndex = await ethers.getContractAt( + InstaIndex.abi, + hre.network.config.InstaIndex + ); + instaList = await ethers.getContractAt( + InstaList.abi, + hre.network.config.InstaList + ); + getCdps = await ethers.getContractAt( + GetCdps.abi, + hre.network.config.GetCdps + ); + dssCdpManager = await ethers.getContractAt( + DssCdpManager.abi, + hre.network.config.DssCdpManager + ); + vat = new ethers.Contract(await dssCdpManager.vat(), Vat.abi, userWallet); + DAI = await ethers.getContractAt(IERC20.abi, hre.network.config.DAI); + + // ========== Test Setup ============ + conditionIsDestVaultWillBeSafe = await ethers.getContract( + "ConditionIsDestVaultWillBeSafe" + ); + + // Create DeFi Smart 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) + ); + + // Create/Deposit/Borrow a Vault + const openVault = await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "open", + inputs: ["ETH-A"], + }); + + await dsa.cast([hre.network.config.ConnectMaker], [openVault], userAddress); + + let cdps = await getCdps.getCdpsAsc(dssCdpManager.address, dsa.address); + cdpId = 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: [cdpId, ethers.utils.parseEther("10"), 0, 0], + }), + ], + userAddress, + { + value: ethers.utils.parseEther("10"), + } + ); + await dsa.cast( + [hre.network.config.ConnectMaker], + [ + await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "borrow", + inputs: [cdpId, ethers.utils.parseUnits("1000", 18), 0, 0], + }), + ], + userAddress + ); + + expect(await DAI.balanceOf(dsa.address)).to.be.equal( + ethers.utils.parseEther("1000") + ); + + ethBIlk = ethers.utils.formatBytes32String("ETH-B"); + ilk = await vat.ilks(ethBIlk); + amountToBorrow = ethers.utils.parseUnits("100", 18); + }); + + it("#1: ok should return DebtBridgeNotAffordable when the gas fees exceed a define amount", async function () { + const conditionData = await conditionIsDestVaultWillBeSafe.getConditionData( + cdpId, + 0, + "ETH-B" + ); + expect( + await conditionIsDestVaultWillBeSafe.ok(0, conditionData, 0) + ).to.be.equal("OK"); + }); + + it("#2: New Vault Case : isDestVaultWillBeSafe should return false when col is lower than borrow amount / spot", async function () { + var amountOfColToDepo = amountToBorrow + .mul(ilk[1]) + .div(ethers.utils.parseUnits("1", 27)); + amountOfColToDepo = amountOfColToDepo + .sub(amountOfColToDepo.div(ethers.utils.parseUnits("10", 0))) + .mul(ethers.utils.parseUnits("1", 27)) + .div(ilk[2]); + + expect( + await conditionIsDestVaultWillBeSafe.isDestVaultWillBeSafe( + 0, + amountToBorrow, + amountOfColToDepo, + "ETH-B" + ) + ).to.be.false; + }); + + it("#3: New Vault Case : isDestVaultWillBeSafe should return true when col is greater than borrow amount / spot", async function () { + let amountOfColToDepo = amountToBorrow + .mul(ilk[1]) + .div(ethers.utils.parseUnits("1", 27)); + amountOfColToDepo = amountOfColToDepo + .add(amountOfColToDepo.div(ethers.utils.parseUnits("10", 0))) + .mul(ethers.utils.parseUnits("1", 27)) + .div(ilk[2]); + + expect( + await conditionIsDestVaultWillBeSafe.isDestVaultWillBeSafe( + 0, + amountToBorrow, + amountOfColToDepo, + "ETH-B" + ) + ).to.be.true; + }); + + it("#4: Old Vault Case : isDestVaultWillBeSafe should return false when col is lower than borrow amount / spot", async function () { + const openVault = await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "open", + inputs: ["ETH-B"], + }); + + await dsa.cast([hre.network.config.ConnectMaker], [openVault], userAddress); + + const cdpIdB = String( + (await getCdps.getCdpsAsc(dssCdpManager.address, dsa.address)).ids[1] + ); + + let amountOfColToDepo = amountToBorrow + .mul(ilk[1]) + .div(ethers.utils.parseUnits("1", 27)); + amountOfColToDepo = amountOfColToDepo + .sub(amountOfColToDepo.div(ethers.utils.parseUnits("10", 0))) + .mul(ethers.utils.parseUnits("1", 27)) + .div(ilk[2]); + + expect( + await conditionIsDestVaultWillBeSafe.isDestVaultWillBeSafe( + cdpIdB, + amountToBorrow, + amountOfColToDepo, + "ETH-B" + ) + ).to.be.false; + }); + + it("#5: Old Vault Case : isDestVaultWillBeSafe should return true when col is lower than borrow amount / spot", async function () { + const openVault = await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "open", + inputs: ["ETH-B"], + }); + + await dsa.cast([hre.network.config.ConnectMaker], [openVault], userAddress); + + const cdpIdB = String( + (await getCdps.getCdpsAsc(dssCdpManager.address, dsa.address)).ids[1] + ); + + let amountOfColToDepo = amountToBorrow + .mul(ilk[1]) + .div(ethers.utils.parseUnits("1", 27)); + amountOfColToDepo = amountOfColToDepo + .add(amountOfColToDepo.div(ethers.utils.parseUnits("10", 0))) + .mul(ethers.utils.parseUnits("1", 27)) + .div(ilk[2]); + + expect( + await conditionIsDestVaultWillBeSafe.isDestVaultWillBeSafe( + cdpIdB, + amountToBorrow, + amountOfColToDepo, + "ETH-B" + ) + ).to.be.true; + }); + + it("#6: Old Vault Case with existing deposit : isDestVaultWillBeSafe should return true when col is lower than borrow amount / spot due to initial deposit on Vault B", async function () { + const openVault = await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "open", + inputs: ["ETH-B"], + }); + + await dsa.cast([hre.network.config.ConnectMaker], [openVault], userAddress); + + const cdpIdB = String( + (await getCdps.getCdpsAsc(dssCdpManager.address, dsa.address)).ids[1] + ); + + let amountOfColToDepo = amountToBorrow + .mul(ilk[1]) + .div(ethers.utils.parseUnits("1", 27)); + const tenPercentOfAmountOfColToDepo = amountOfColToDepo.div( + ethers.utils.parseUnits("10", 0) + ); + amountOfColToDepo = amountOfColToDepo + .sub(tenPercentOfAmountOfColToDepo) + .mul(ethers.utils.parseUnits("1", 27)) + .div(ilk[2]); + + // Deposit For ETH-B Vault + await dsa.cast( + [hre.network.config.ConnectMaker], + [ + await hre.run("abi-encode-withselector", { + abi: ConnectMaker.abi, + functionname: "deposit", + inputs: [cdpIdB, tenPercentOfAmountOfColToDepo, 0, 0], + }), + ], + userAddress, + { + value: tenPercentOfAmountOfColToDepo, + } + ); + + expect( + await conditionIsDestVaultWillBeSafe.isDestVaultWillBeSafe( + cdpIdB, + amountToBorrow, + amountOfColToDepo, + "ETH-B" + ) + ).to.be.true; + }); +}); diff --git a/test/unit/functions/0_FGelatoDebtBridge.test.js b/test/unit/functions/0_FGelatoDebtBridge.test.js index 52a0d18..57c1e2f 100644 --- a/test/unit/functions/0_FGelatoDebtBridge.test.js +++ b/test/unit/functions/0_FGelatoDebtBridge.test.js @@ -67,9 +67,7 @@ describe("FGelatoDebtBridge Unit Tests", function () { it("getFlashLoanRoute should revert with FGelatoDebtBridge._getFlashLoanRoute: illiquid", async function () { const rData = await instaPoolResolver.getTokenLimit(DAI); - const daiAmtToBorrow = ethers.utils - .parseUnits("1000", 18) - .add(rData.compound); + const daiAmtToBorrow = ethers.utils.parseUnits("1000", 18).add(rData.aave); await expect( fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow) diff --git a/hardhat/config/mainnet-deployments.js b/tool/config/mainnet-deployments.js similarity index 100% rename from hardhat/config/mainnet-deployments.js rename to tool/config/mainnet-deployments.js