diff --git a/contracts/constants/CInstaDapp.sol b/contracts/constants/CInstaDapp.sol index 3e4c287..88a677b 100644 --- a/contracts/constants/CInstaDapp.sol +++ b/contracts/constants/CInstaDapp.sol @@ -15,4 +15,4 @@ address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Insta Pool address constant INSTA_POOL_RESOLVER = 0xa004a5afBa04b74037E9E52bA1f7eb02b5E61509; -uint256 constant ROUTE_1_3_TOLERANCE = 1005e15; \ No newline at end of file +uint256 constant ROUTE_1_TOLERANCE = 1005e15; \ No newline at end of file diff --git a/contracts/contracts/connectors/ConnectGelatoDataFullRefinanceMaker.sol b/contracts/contracts/connectors/ConnectGelatoDataFullRefinanceMaker.sol index 3b074cb..6b869c1 100644 --- a/contracts/contracts/connectors/ConnectGelatoDataFullRefinanceMaker.sol +++ b/contracts/contracts/connectors/ConnectGelatoDataFullRefinanceMaker.sol @@ -3,7 +3,7 @@ pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import {GelatoBytes} from "../../lib/GelatoBytes.sol"; -import {sub, wmul} from "../../vendor/DSMath.sol"; +import {sub} from "../../vendor/DSMath.sol"; import { AccountInterface, ConnectorInterface diff --git a/contracts/contracts/mocks/FGelatoDebtBridgeMock.sol b/contracts/contracts/mocks/FGelatoDebtBridgeMock.sol index 739cd44..77da308 100644 --- a/contracts/contracts/mocks/FGelatoDebtBridgeMock.sol +++ b/contracts/contracts/mocks/FGelatoDebtBridgeMock.sol @@ -3,7 +3,11 @@ pragma solidity 0.7.4; import { _wCalcCollateralToWithdraw, - _wCalcDebtToRepay + _wCalcDebtToRepay, + _getFlashLoanRoute, + _getGasCostMakerToMaker, + _getGasCostMakerToCompound, + _getRealisedDebt } from "../../functions/gelato/FGelatoDebtBridge.sol"; contract FGelatoDebtBridgeMock { @@ -38,4 +42,36 @@ contract FGelatoDebtBridgeMock { _wDaiDebtOnMaker ); } + + function getFlashLoanRoute(address _tokenA, uint256 _wTokenADebtToMove) + public + view + returns (uint256) + { + return _getFlashLoanRoute(_tokenA, _wTokenADebtToMove); + } + + function getGasCostMakerToMaker(bool _newVault, uint256 _route) + public + pure + returns (uint256) + { + return _getGasCostMakerToMaker(_newVault, _route); + } + + function getGasCostMakerToCompound(uint256 _route) + public + pure + returns (uint256) + { + return _getGasCostMakerToCompound(_route); + } + + function getRealisedDebt(uint256 _debtToMove) + public + pure + returns (uint256) + { + return _getRealisedDebt(_debtToMove); + } } diff --git a/contracts/contracts/resolvers/MakerResolver.sol b/contracts/contracts/resolvers/MakerResolver.sol index cf8c738..c9f737b 100644 --- a/contracts/contracts/resolvers/MakerResolver.sol +++ b/contracts/contracts/resolvers/MakerResolver.sol @@ -2,14 +2,14 @@ pragma solidity 0.7.4; import { - _getMakerVaultDebt, + _getMakerRawVaultDebt, _getMakerVaultCollateralBalance } from "../../functions/dapps/FMaker.sol"; contract MakerResolver { /// @dev Return Debt in wad of the vault associated to the vaultId. function getMakerVaultDebt(uint256 _vaultId) public view returns (uint256) { - return _getMakerVaultDebt(_vaultId); + return _getMakerRawVaultDebt(_vaultId); } /// @dev Return Collateral in wad of the vault associated to the vaultId. diff --git a/contracts/functions/dapps/FMaker.sol b/contracts/functions/dapps/FMaker.sol index c27cb2d..9688896 100644 --- a/contracts/functions/dapps/FMaker.sol +++ b/contracts/functions/dapps/FMaker.sol @@ -21,6 +21,20 @@ function _getMakerVaultDebt(uint256 _vaultId) view returns (uint256 wad) { wad = mul(wad, RAY) < rad ? wad + 1 : wad; } +function _getMakerRawVaultDebt(uint256 _vaultId) view returns (uint256 tab) { + IMcdManager manager = IMcdManager(MCD_MANAGER); + + (bytes32 ilk, address urn) = _getVaultData(manager, _vaultId); + IVat vat = IVat(manager.vat()); + (, uint256 rate, , , ) = vat.ilks(ilk); + (, uint256 art) = vat.urns(ilk, urn); + + uint256 rad = mul(art, rate); + + tab = rad / RAY; + tab = mul(tab, RAY) < rad ? tab + 1 : tab; +} + function _getMakerVaultCollateralBalance(uint256 _vaultId) view returns (uint256) diff --git a/contracts/functions/gelato/FGelatoDebtBridge.sol b/contracts/functions/gelato/FGelatoDebtBridge.sol index 5c6b8c9..a1c1c1e 100644 --- a/contracts/functions/gelato/FGelatoDebtBridge.sol +++ b/contracts/functions/gelato/FGelatoDebtBridge.sol @@ -5,7 +5,7 @@ pragma experimental ABIEncoderV2; import {add, sub, wmul, wdiv} from "../../vendor/DSMath.sol"; import { INSTA_POOL_RESOLVER, - ROUTE_1_3_TOLERANCE + ROUTE_1_TOLERANCE } from "../../constants/CInstaDapp.sol"; import {GAS_COSTS_FOR_FULL_REFINANCE} from "../../constants/CDebtBridge.sol"; import { @@ -77,6 +77,7 @@ function _getGasCostMakerToMaker(bool _newVault, uint256 _route) pure returns (uint256) { + _checkRouteIndex(_route); return _newVault ? add(GAS_COSTS_FOR_FULL_REFINANCE()[_route], 0) @@ -84,9 +85,17 @@ function _getGasCostMakerToMaker(bool _newVault, uint256 _route) } function _getGasCostMakerToCompound(uint256 _route) pure returns (uint256) { + _checkRouteIndex(_route); return GAS_COSTS_FOR_FULL_REFINANCE()[_route]; } function _getRealisedDebt(uint256 _debtToMove) pure returns (uint256) { - return wmul(_debtToMove, ROUTE_1_3_TOLERANCE); + return wmul(_debtToMove, ROUTE_1_TOLERANCE); +} + +function _checkRouteIndex(uint256 _route) pure { + require( + _route <= 4, + "FGelatoDebtBridge._getGasCostMakerToMaker: invalid route index" + ); } diff --git a/test/2_Full-Debt-Bridge-Maker-Compound.test.js b/test/2_Full-Debt-Bridge-Maker-Compound.test.js index a1642a4..e9cede3 100644 --- a/test/2_Full-Debt-Bridge-Maker-Compound.test.js +++ b/test/2_Full-Debt-Bridge-Maker-Compound.test.js @@ -274,15 +274,9 @@ describe("Full Debt Bridge refinancing loan from Maker to Compound", function () compoundPosition[0].borrowBalanceStoredUser ); } else { - expect(debtOnMakerBefore).to.be.equal( - compoundPosition[0].borrowBalanceStoredUser - ); - - // We should not have borrowed DAI on maker - const debtOnMakerAfter = await contracts.makerResolver.getMakerVaultDebt( - vaultId - ); - expect(debtOnMakerAfter).to.be.equal(ethers.constants.Zero); + expect( + debtOnMakerBefore.sub(compoundPosition[0].borrowBalanceStoredUser) + ).to.be.lt(ethers.utils.parseUnits("2", 0)); } // Estimated amount of collateral should be equal to the actual one read on compound contracts @@ -292,12 +286,16 @@ describe("Full Debt Bridge refinancing loan from Maker to Compound", function () ) ).to.be.lt(ethers.utils.parseUnits("1", 12)); - const collateralOnMakerAfter = await contracts.makerResolver.getMakerVaultCollateralBalance( + const collateralOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultCollateralBalance( vaultId ); // in Ether. + const debtOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultDebt( + vaultId + ); - // We should not have deposited ether on it. - expect(collateralOnMakerAfter).to.be.equal(ethers.constants.Zero); + // We should not have deposited ether or borrowed DAI on maker. + expect(collateralOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); + expect(debtOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); // DSA contain 1000 DAI expect(await contracts.DAI.balanceOf(contracts.dsa.address)).to.be.equal( diff --git a/test/3_Full-Debt-Bridge-ETHA-ETHB-With-Vault-Creation.test.js b/test/3_Full-Debt-Bridge-ETHA-ETHB-With-Vault-Creation.test.js index fbd2bb0..b480ea0 100644 --- a/test/3_Full-Debt-Bridge-ETHA-ETHB-With-Vault-Creation.test.js +++ b/test/3_Full-Debt-Bridge-ETHA-ETHB-With-Vault-Creation.test.js @@ -275,12 +275,6 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B with vault creat expect(debtOnMakerBefore).to.be.lte(debtOnMakerVaultB); } else { expect(debtOnMakerBefore).to.be.equal(debtOnMakerVaultB); - - // We should not have borrowed DAI on maker - const debtOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultDebt( - vaultAId - ); - expect(debtOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); } // Estimated amount of collateral should be equal to the actual one read on compound contracts @@ -289,9 +283,13 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B with vault creat const collateralOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultCollateralBalance( vaultAId ); // in Ether. + const debtOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultDebt( + vaultAId + ); - // We should not have deposited ether on it. + // We should not have deposited ether or borrowed DAI on maker. expect(collateralOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); + expect(debtOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); // DSA has maximum 2 wei DAI in it due to maths inaccuracies expect(await contracts.DAI.balanceOf(contracts.dsa.address)).to.be.equal( diff --git a/test/4_Full-Debt-Bridge-ETHA-ETHB.test.js b/test/4_Full-Debt-Bridge-ETHA-ETHB.test.js index ee6c493..2a2bbc0 100644 --- a/test/4_Full-Debt-Bridge-ETHA-ETHB.test.js +++ b/test/4_Full-Debt-Bridge-ETHA-ETHB.test.js @@ -275,12 +275,6 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () { expect(debtOnMakerBefore).to.be.lte(debtOnMakerVaultB); } else { expect(debtOnMakerBefore).to.be.equal(debtOnMakerVaultB); - - // We should not have borrowed DAI on maker - const debtOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultDebt( - vaultAId - ); - expect(debtOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); } // Estimated amount of collateral should be equal to the actual one read on compound contracts @@ -289,9 +283,13 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () { const collateralOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultCollateralBalance( vaultAId ); // in Ether. + const debtOnMakerOnVaultAAfter = await contracts.makerResolver.getMakerVaultDebt( + vaultAId + ); - // We should not have deposited ether on it. + // We should not have deposited ether or borrowed DAI on maker. expect(collateralOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); + expect(debtOnMakerOnVaultAAfter).to.be.equal(ethers.constants.Zero); // DSA has maximum 2 wei DAI in it due to maths inaccuracies expect(await contracts.DAI.balanceOf(contracts.dsa.address)).to.be.equal( diff --git a/test/unit_tests/5_FGelatoDebtBridge.test.js b/test/unit_tests/5_FGelatoDebtBridge.test.js new file mode 100644 index 0000000..ac33d27 --- /dev/null +++ b/test/unit_tests/5_FGelatoDebtBridge.test.js @@ -0,0 +1,175 @@ +const {expect} = require("chai"); +const hre = require("hardhat"); +const {deployments, ethers} = hre; + +const InstaPoolResolver = require("../../artifacts/contracts/interfaces/InstaDapp/resolvers/IInstaPoolResolver.sol/IInstaPoolResolver.json"); +const DAI = hre.network.config.DAI; + +describe("Debt Partial Refinance Math 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 fGelatoDebtBridgeMock; + let instaPoolResolver; + beforeEach(async function () { + await deployments.fixture(); + + instaPoolResolver = await ethers.getContractAt( + InstaPoolResolver.abi, + hre.network.config.InstaPoolResolver + ); + + fGelatoDebtBridgeMock = await ethers.getContract("FGelatoDebtBridgeMock"); + }); + + it("getFlashLoanRoute should return 0 when dydx has enough liquidity", async function () { + // const rData = instaPoolResolver.getTokenLimit(DAI); + const daiAmtToBorrow = ethers.utils.parseUnits("1000", 18); + + expect( + await fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow) + ).to.be.equal(0); + }); + + it("getFlashLoanRoute should return 1 when maker has enough liquidity and cheaper protocol didn't have enough liquidity", async function () { + const rData = await instaPoolResolver.getTokenLimit(DAI); + const daiAmtToBorrow = ethers.utils.parseUnits("1000", 18).add(rData.dydx); + + expect( + await fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow) + ).to.be.equal(1); + }); + + it("getFlashLoanRoute should return 2 when compound has enough liquidity and cheaper protocol didn't have enough liquidity", async function () { + const rData = await instaPoolResolver.getTokenLimit(DAI); + const daiAmtToBorrow = ethers.utils.parseUnits("1000", 18).add(rData.maker); + + expect( + await fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow) + ).to.be.equal(2); + }); + + // Seems aave has less liquidity than compound, is it always the case? If yes, why we should use this protocol. + + // it("getFlashLoanRoute should return 3 when aave has enough liquidity and cheaper protocol didn't have enough liquidity", async function () { + // const rData = await instaPoolResolver.getTokenLimit(DAI); + // console.log(String(rData.dydx)); + // console.log(String(rData.maker)); + // console.log(String(rData.compound)); + // console.log(String(rData.aave)); + // const daiAmtToBorrow = ethers.utils.parseUnits("1000", 18).add(rData.compound); + + // expect(await fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow)).to.be.equal(3); + // }) + + 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); + + await expect( + fGelatoDebtBridgeMock.getFlashLoanRoute(DAI, daiAmtToBorrow) + ).to.be.revertedWith("FGelatoDebtBridge._getFlashLoanRoute: illiquid"); + }); + + it("getGasCostMakerToMaker should return 2519000 gas limit for route 0 (Dydx) and new vault", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(true, 0) + ).to.be.equal(2519000); + }); + + it("getGasCostMakerToMaker should return 2519000 gas limit for route 0 (Dydx)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(false, 0) + ).to.be.equal(2519000); + }); + + it("getGasCostMakerToMaker should return 3140500 gas limit for route 1 (maker) and new vault", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(true, 1) + ).to.be.equal(3140500); + }); + + it("getGasCostMakerToMaker should return 3140500 gas limit for route 1 (maker)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(false, 1) + ).to.be.equal(3140500); + }); + + it("getGasCostMakerToMaker should return 3971000 gas limit for route 2 (compound) and new vault", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(true, 2) + ).to.be.equal(3971000); + }); + + it("getGasCostMakerToMaker should return 3971000 gas limit for route 2 (compound)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(false, 2) + ).to.be.equal(3971000); + }); + + it("getGasCostMakerToMaker should return 4345000 gas limit for route 3 (aave) and new vault", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(true, 3) + ).to.be.equal(4345000); + }); + + it("getGasCostMakerToMaker should return 4345000 gas limit for route 3 (aave)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToMaker(false, 3) + ).to.be.equal(4345000); + }); + + it("getGasCostMakerToMaker should revert with invalid route index when the inputed route exceed 4", async function () { + await expect( + fGelatoDebtBridgeMock.getGasCostMakerToMaker(true, 5) + ).to.be.revertedWith( + "FGelatoDebtBridge._getGasCostMakerToMaker: invalid route index" + ); + }); + + it("getGasCostMakerToCompound should return 2519000 gas limit for route 0 (Dydx)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToCompound(0) + ).to.be.equal(2519000); + }); + + it("getGasCostMakerToCompound should return 3140500 gas limit for route 1 (Maker)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToCompound(1) + ).to.be.equal(3140500); + }); + + it("getGasCostMakerToCompound should return 3971000 gas limit for route 2 (Compound)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToCompound(2) + ).to.be.equal(3971000); + }); + + it("getGasCostMakerToCompound should return 4345000 gas limit for route 3 (Aave)", async function () { + expect( + await fGelatoDebtBridgeMock.getGasCostMakerToCompound(3) + ).to.be.equal(4345000); + }); + + it("getGasCostMakerToCompound should revert with invalid route index when the inputed route exceed 4", async function () { + await expect( + fGelatoDebtBridgeMock.getGasCostMakerToCompound(5) + ).to.be.revertedWith( + "FGelatoDebtBridge._getGasCostMakerToMaker: invalid route index" + ); + }); + + it("getRealisedDebt should increase the inputed uint by 0,5%", async function () { + const debtToMove = ethers.utils.parseUnits("1000", 18); + const expectedRealisedDebt = ethers.utils.parseUnits("1005", 18); + + expect(await fGelatoDebtBridgeMock.getRealisedDebt(debtToMove)).to.be.equal( + expectedRealisedDebt + ); + }); +});