diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index 87a17a69..9eef0744 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -23,6 +23,7 @@ jobs: - ExchangeRate - Health - LibSummary + - LiquidateBuffer - Liveness - Reentrancy - Reverts diff --git a/certora/confs/LiquidateBuffer.conf b/certora/confs/LiquidateBuffer.conf new file mode 100644 index 00000000..71e1cb15 --- /dev/null +++ b/certora/confs/LiquidateBuffer.conf @@ -0,0 +1,19 @@ +{ + "files": [ + "certora/helpers/MorphoHarness.sol", + "certora/helpers/Util.sol" + ], + "solc": "solc-0.8.19", + "verify": "MorphoHarness:certora/specs/LiquidateBuffer.spec", + "prover_args": [ + "-depth 5", + "-mediumTimeout 20", + "-timeout 3600", + "-adaptiveSolverConfig false", + "-smt_nonLinearArithmetic true", + "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + ], + "rule_sanity": "basic", + "server": "production", + "msg": "Morpho Blue Liquidate Buffer" +} diff --git a/certora/helpers/Util.sol b/certora/helpers/Util.sol index cef51dea..bdc64a81 100644 --- a/certora/helpers/Util.sol +++ b/certora/helpers/Util.sol @@ -8,6 +8,7 @@ import "../../src/libraries/UtilsLib.sol"; contract Util { using MarketParamsLib for MarketParams; + using MathLib for uint256; function wad() external pure returns (uint256) { return WAD; @@ -17,6 +18,15 @@ contract Util { return MAX_FEE; } + function oraclePriceScale() external pure returns (uint256) { + return ORACLE_PRICE_SCALE; + } + + function lif(uint256 lltv) external pure returns (uint256) { + return + UtilsLib.min(MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - lltv))); + } + function libId(MarketParams memory marketParams) external pure returns (Id) { return marketParams.id(); } diff --git a/certora/specs/LiquidateBuffer.spec b/certora/specs/LiquidateBuffer.spec new file mode 100644 index 00000000..a98e923d --- /dev/null +++ b/certora/specs/LiquidateBuffer.spec @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +import "Health.spec"; + +methods { + function Util.lif(uint256) external returns (uint256) envfree; + function Util.oraclePriceScale() external returns (uint256) envfree; + function Util.wad() external returns (uint256) envfree; + function Morpho._isHealthy(MorphoHarness.MarketParams memory, MorphoHarness.Id,address) internal returns (bool) => NONDET; + function Morpho._accrueInterest(MorphoHarness.MarketParams memory, MorphoHarness.Id) internal => NONDET; +} + +rule liquidateImprovePosition(env e, MorphoHarness.MarketParams marketParams, address borrower, uint256 seizedAssetsInput, uint256 repaidSharesInput, bytes data) { + // Assume no callback for simplicity. + require data.length == 0; + + MorphoHarness.Id id = Util.libId(marketParams); + + // We place ourselves at the last block for getting the following variables. + require lastUpdate(id) == e.block.timestamp; + + uint256 borrowerShares = borrowShares(id, borrower); + require borrowerShares <= totalBorrowShares(id); + + uint256 borrowerCollateral = collateral(id, borrower); + uint256 collateralPrice = mockPrice(); + uint256 lif = Util.lif(marketParams.lltv); + + uint256 borrowerAssets = summaryMulDivUp(borrowerShares, virtualTotalBorrowAssets(id), virtualTotalBorrowShares(id)); + uint256 borrowerCollateralQuoted = summaryMulDivDown(borrowerCollateral, collateralPrice, Util.oraclePriceScale()); + + require summaryMulDivUp(lif, borrowerAssets, Util.wad()) < borrowerCollateralQuoted; + assert borrowerCollateral * collateralPrice * virtualTotalBorrowShares(id) * Util.wad() > borrowerShares * Util.oraclePriceScale() * virtualTotalBorrowAssets(id) * lif; + + uint256 seizedAssets; + uint256 repaidAssets; + (seizedAssets, _) = liquidate(e, marketParams, borrower, seizedAssetsInput, repaidSharesInput, data); + + // uint256 newBorrowerShares = borrowShares(id, borrower); + uint256 repaidShares = assert_uint256(borrowerShares - newBorrowerShares); + + require !priceChanged; + require collateral(id, borrower) != 0; + assert repaidShares * borrowerCollateral >= seizedAssets * borrowerShares; + // assert borrowerShares * newBorrowerCollateral >= newBorrowerShares * borrowerCollateral; + // assert newTotalShares * OldVirtualTotalBorrowAssets >= newTotalAssets * OldVirtualTotalBorrowShares; + +}