From aec989b2d758eebf2dc2861a552f3d825144e605 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Tue, 2 Apr 2024 19:20:07 +0200 Subject: [PATCH] test bid/ask priceing --- test/mocks/MockPriceOracle.sol | 46 ++++++-- test/unit/evault/DToken.t.sol | 2 +- .../modules/BalanceForwarder/hooks.t.sol | 2 +- .../modules/Governance/convertFees.t.sol | 2 +- .../modules/Governance/interestRates.t.sol | 2 +- .../evault/modules/Governance/pause.t.sol | 4 +- test/unit/evault/modules/Vault/borrow.t.sol | 2 +- test/unit/evault/modules/Vault/caps.t.sol | 2 +- .../evault/modules/Vault/liquidation.t.sol | 101 ++++++++++++++++-- test/unit/evault/modules/Vault/ltv.t.sol | 2 +- test/unit/evault/modules/Vault/withdraw.t.sol | 2 +- 11 files changed, 143 insertions(+), 24 deletions(-) diff --git a/test/mocks/MockPriceOracle.sol b/test/mocks/MockPriceOracle.sol index 2ef32675..226d7b3b 100644 --- a/test/mocks/MockPriceOracle.sol +++ b/test/mocks/MockPriceOracle.sol @@ -10,18 +10,21 @@ contract MockPriceOracle { error PO_Overflow(); error PO_NoPath(); - mapping(address base => mapping(address quote => uint256)) prices; + mapping(address base => mapping(address quote => uint256)) price; + mapping(address base => mapping(address quote => Prices)) prices; + + struct Prices { + bool set; + uint256 bid; + uint256 ask; + } function name() external pure returns (string memory) { return "MockPriceOracle"; } function getQuote(uint256 amount, address base, address quote) public view returns (uint256 out) { - uint256 price = prices[base][quote]; - (bool success,) = base.staticcall(abi.encodeCall(IERC4626.asset, ())); - if (base.code.length > 0 && success) amount = IEVault(base).convertToAssets(amount); - - return amount * price / 1e18; + return calculateQuote(base, amount, price[resolveUnderlying(base)][quote]); } function getQuotes(uint256 amount, address base, address quote) @@ -29,15 +32,42 @@ contract MockPriceOracle { view returns (uint256 bidOut, uint256 askOut) { + if (prices[resolveUnderlying(base)][quote].set) { + return ( + calculateQuote(base, amount, prices[resolveUnderlying(base)][quote].bid), + calculateQuote(base, amount, prices[resolveUnderlying(base)][quote].ask) + ); + } + bidOut = askOut = getQuote(amount, base, quote); } ///// Mock functions - function setPrice(address base, address quote, uint256 price) external { - prices[base][quote] = price; + function setPrice(address base, address quote, uint256 newPrice) external { + price[resolveUnderlying(base)][quote] = newPrice; + } + + function setPrices(address base, address quote, uint256 newBid, uint256 newAsk) external { + prices[resolveUnderlying(base)][quote] = Prices({set: true, bid: newBid, ask: newAsk}); } + function calculateQuote(address base, uint256 amount, uint256 p) internal view returns (uint256) { + (bool success,) = base.staticcall(abi.encodeCall(IERC4626.asset, ())); + if (base.code.length > 0 && success) amount = IEVault(base).convertToAssets(amount); + + return amount * p / 1e18; + } + + function resolveUnderlying(address asset) internal view returns (address) { + if (asset.code.length > 0) { + (bool success, bytes memory data) = asset.staticcall(abi.encodeCall(IERC4626.asset, ())); + if (success) return abi.decode(data, (address)); + } + + return asset; + } + function testExcludeFromCoverage() public pure { return; } diff --git a/test/unit/evault/DToken.t.sol b/test/unit/evault/DToken.t.sol index 0f833f7f..137a83c5 100644 --- a/test/unit/evault/DToken.t.sol +++ b/test/unit/evault/DToken.t.sol @@ -174,6 +174,6 @@ contract DTokenTest is EVaultTestBase { vm.stopPrank(); oracle.setPrice(address(assetTST), unitOfAccount, 1 ether); - oracle.setPrice(address(eTST2), unitOfAccount, 1000 ether); + oracle.setPrice(address(assetTST2), unitOfAccount, 1000 ether); } } diff --git a/test/unit/evault/modules/BalanceForwarder/hooks.t.sol b/test/unit/evault/modules/BalanceForwarder/hooks.t.sol index 93463ff0..4b4f6a72 100644 --- a/test/unit/evault/modules/BalanceForwarder/hooks.t.sol +++ b/test/unit/evault/modules/BalanceForwarder/hooks.t.sol @@ -295,7 +295,7 @@ contract BalanceForwarderTest_Hooks is EVaultTestBase { evc.enableCollateral(user, address(eTST2)); oracle.setPrice(address(assetTST), unitOfAccount, 1 ether); - oracle.setPrice(address(eTST2), unitOfAccount, 1 ether); + oracle.setPrice(address(assetTST2), unitOfAccount, 1 ether); vm.stopPrank(); } } diff --git a/test/unit/evault/modules/Governance/convertFees.t.sol b/test/unit/evault/modules/Governance/convertFees.t.sol index 67f8a81b..a21573f6 100644 --- a/test/unit/evault/modules/Governance/convertFees.t.sol +++ b/test/unit/evault/modules/Governance/convertFees.t.sol @@ -19,7 +19,7 @@ contract Governance_ConvertFees is EVaultTestBase { // Setup oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); eTST.setLTV(address(eTST2), 0.9e4, 0); diff --git a/test/unit/evault/modules/Governance/interestRates.t.sol b/test/unit/evault/modules/Governance/interestRates.t.sol index 31109254..1cf0a89d 100644 --- a/test/unit/evault/modules/Governance/interestRates.t.sol +++ b/test/unit/evault/modules/Governance/interestRates.t.sol @@ -19,7 +19,7 @@ contract Governance_InterestRates is EVaultTestBase { // Setup oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); eTST.setLTV(address(eTST2), 0.9e4, 0); diff --git a/test/unit/evault/modules/Governance/pause.t.sol b/test/unit/evault/modules/Governance/pause.t.sol index 36180392..4082f89e 100644 --- a/test/unit/evault/modules/Governance/pause.t.sol +++ b/test/unit/evault/modules/Governance/pause.t.sol @@ -27,7 +27,7 @@ contract Governance_PauseOps is EVaultTestBase { // ----------------- Setup vaults -------------------- eTST.setLTV(address(eTST2), 0.9e4, 0); oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); // ----------------- Setup depositor ----------------- vm.startPrank(depositor); assetTST.mint(depositor, type(uint256).max); @@ -345,7 +345,7 @@ contract Governance_PauseOps is EVaultTestBase { eTST.borrow(8 * MINT_AMOUNT / 10, borrower); vm.stopPrank(); - oracle.setPrice(address(eTST2), unitOfAccount, 0.5e17); + oracle.setPrice(address(assetTST2), unitOfAccount, 0.5e17); vm.startPrank(liquidator); evc.enableCollateral(liquidator, address(eTST2)); diff --git a/test/unit/evault/modules/Vault/borrow.t.sol b/test/unit/evault/modules/Vault/borrow.t.sol index d37d462b..044d794a 100644 --- a/test/unit/evault/modules/Vault/borrow.t.sol +++ b/test/unit/evault/modules/Vault/borrow.t.sol @@ -28,7 +28,7 @@ contract VaultTest_Borrow is EVaultTestBase { // Setup oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); eTST.setLTV(address(eTST2), 0.9e4, 0); diff --git a/test/unit/evault/modules/Vault/caps.t.sol b/test/unit/evault/modules/Vault/caps.t.sol index 4ab2ebfb..0dab9b9b 100644 --- a/test/unit/evault/modules/Vault/caps.t.sol +++ b/test/unit/evault/modules/Vault/caps.t.sol @@ -438,7 +438,7 @@ contract VaultTest_Caps is EVaultTestBase { evc.enableCollateral(user, address(eTST2)); oracle.setPrice(address(assetTST), unitOfAccount, 1 ether); - oracle.setPrice(address(eTST2), unitOfAccount, 1000 ether); + oracle.setPrice(address(assetTST2), unitOfAccount, 1000 ether); vm.stopPrank(); } } diff --git a/test/unit/evault/modules/Vault/liquidation.t.sol b/test/unit/evault/modules/Vault/liquidation.t.sol index 34da4bd4..ceecb72b 100644 --- a/test/unit/evault/modules/Vault/liquidation.t.sol +++ b/test/unit/evault/modules/Vault/liquidation.t.sol @@ -56,10 +56,8 @@ contract VaultLiquidation_Test is EVaultTestBase { oracle.setPrice(address(assetTST), unitOfAccount, 2.2e18); oracle.setPrice(address(assetTST2), unitOfAccount, 0.4e18); - oracle.setPrice(address(eTST), unitOfAccount, 0.4e18); - oracle.setPrice(address(eTST2), unitOfAccount, 0.4e18); - oracle.setPrice(address(eTST3), unitOfAccount, 2.2e18); - oracle.setPrice(address(eWETH), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST3), unitOfAccount, 2.2e18); + oracle.setPrice(address(assetWETH), unitOfAccount, 1e18); startHoax(lender); @@ -677,7 +675,7 @@ contract VaultLiquidation_Test is EVaultTestBase { (uint256 collateralValue, uint256 liabilityValue) = eTST.accountLiquidity(borrower, false); assertApproxEqAbs(collateralValue * 1e18 / liabilityValue, 1.09e18, 0.01e18); - oracle.setPrice(address(eTST2), unitOfAccount, 0); + oracle.setPrice(address(assetTST2), unitOfAccount, 0); (collateralValue, liabilityValue) = eTST.accountLiquidity(borrower, false); assertEq(collateralValue, 0); @@ -904,7 +902,7 @@ contract VaultLiquidation_Test is EVaultTestBase { eTST2.deposit(1, borrower); oracle.setPrice(address(assetTST), unitOfAccount, 0.001e18); - oracle.setPrice(address(eTST2), unitOfAccount, 15e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 15e18); eTST.borrow(3000, borrower); (uint256 collateralValue, uint256 liabilityValue) = eTST.accountLiquidity(borrower, false); @@ -1181,6 +1179,97 @@ contract VaultLiquidation_Test is EVaultTestBase { vm.revertTo(snapshot); } + function test_liquidationWithBidAskPricing() public { + // set up liquidator to support the debt + startHoax(lender); + evc.enableController(lender, address(eTST)); + evc.enableCollateral(lender, address(eTST3)); + evc.enableCollateral(lender, address(eTST2)); + + startHoax(borrower); + evc.enableController(borrower, address(eTST)); + eTST.borrow(5e18, borrower); + + startHoax(address(this)); + eTST.setLTV(address(eTST3), 0.95e4, 0); + + (uint256 collateralValue, uint256 liabilityValue) = eTST.accountLiquidity(borrower, false); + assertApproxEqAbs(collateralValue * 1e18 / liabilityValue, 1.09e18, 0.01e18); + + // regular liquidation, bid = ask = mid price + + oracle.setPrice(address(assetTST), unitOfAccount, 2.5e18); + + (collateralValue, liabilityValue) = eTST.accountLiquidity(borrower, false); + uint256 healthScore = collateralValue * 1e18 / liabilityValue; + assertApproxEqAbs(healthScore, 0.96e18, 0.001e18); + // liability price bid price changes + + oracle.setPrices(address(assetTST), unitOfAccount, 2.4e18, 2.5e18); + + // no influence + (uint256 newCollateralValue, uint256 newLiabilityValue) = eTST.accountLiquidity(borrower, false); + assertEq(collateralValue, newCollateralValue); + assertEq(liabilityValue, newLiabilityValue); + + // liability ask price changes + + oracle.setPrices(address(assetTST), unitOfAccount, 2.5e18, 2.6e18); + + // liability valued at ask + (newCollateralValue, newLiabilityValue) = eTST.accountLiquidity(borrower, false); + assertEq(collateralValue, newCollateralValue); + assertGt(newLiabilityValue, liabilityValue); + + liabilityValue = newLiabilityValue; + + // collateral ask price changes + + oracle.setPrices(address(assetTST2), unitOfAccount, 0.4e18, 0.5e18); + + (newCollateralValue, newLiabilityValue) = eTST.accountLiquidity(borrower, false); + assertEq(collateralValue, newCollateralValue); + assertEq(liabilityValue, newLiabilityValue); + + // collateral bid price changes + + oracle.setPrices(address(assetTST2), unitOfAccount, 0.3e18, 0.4e18); + + (newCollateralValue, newLiabilityValue) = eTST.accountLiquidity(borrower, false); + assertGt(collateralValue, newCollateralValue); + assertEq(liabilityValue, newLiabilityValue); + + collateralValue = newCollateralValue; + + healthScore = collateralValue * 1e18 / liabilityValue; + assertApproxEqAbs(healthScore, 0.692e18, 0.001e18); + + // The discount is max - 20% on mid point prices + + (uint256 maxRepay, uint256 maxYield) = eTST.checkLiquidation(lender, borrower, address(eTST2)); + + uint256 repayValue = oracle.getQuote(maxRepay, address(eTST), unitOfAccount); + uint256 yieldValue = oracle.getQuote(maxYield, address(eTST2), unitOfAccount); + + assertEq(yieldValue, repayValue * 1e18 / 0.8e18); + + uint256 violatorsOriginalCollateral = eTST2.balanceOf(borrower); + + startHoax(lender); + + // max uint is equivalent to maxRepay + eTST.liquidate(borrower, address(eTST2), type(uint256).max, 0); + + // liquidator: + assertEq(eTST.debtOf(lender), maxRepay); + assertEq(eTST2.balanceOf(lender), maxYield); + + // violator: + startHoax(borrower); + assertEq(eTST.debtOf(borrower), 0); + assertEq(eTST2.balanceOf(borrower), violatorsOriginalCollateral - maxYield); + } + function getRiskAdjustedValue(uint256 amount, uint256 price, uint256 factor) public pure returns (uint256) { return amount * price / 1e18 * factor / 1e18; } diff --git a/test/unit/evault/modules/Vault/ltv.t.sol b/test/unit/evault/modules/Vault/ltv.t.sol index 52a8d425..987b3a70 100644 --- a/test/unit/evault/modules/Vault/ltv.t.sol +++ b/test/unit/evault/modules/Vault/ltv.t.sol @@ -20,7 +20,7 @@ contract VaultTest_LTV is EVaultTestBase { // Setup oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); depositor = makeAddr("depositor"); borrower = makeAddr("borrower"); diff --git a/test/unit/evault/modules/Vault/withdraw.t.sol b/test/unit/evault/modules/Vault/withdraw.t.sol index f2b38bca..0550fdae 100644 --- a/test/unit/evault/modules/Vault/withdraw.t.sol +++ b/test/unit/evault/modules/Vault/withdraw.t.sol @@ -29,7 +29,7 @@ contract VaultTest_withdraw is EVaultTestBase { // Setup oracle.setPrice(address(assetTST), unitOfAccount, 1e18); - oracle.setPrice(address(eTST2), unitOfAccount, 1e18); + oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); eTST.setLTV(address(eTST2), 0.9e4, 0);