@@ -465,19 +465,23 @@ contract MorphoBorrowPosition is IBorrowPosition, IBorrowOffers, Initializable,
465465 /// {_isHealthy}). Called by {_offerPreLiquidate} before the consume and after the repay
466466 /// settles, so the two frames differ only by the fill's real effect on Morpho's totals.
467467 function _positionLtvFrame (uint256 price ) internal view returns (uint256 debtValue , uint256 collateralValue ) {
468- BorrowPositionStorage storage _storage = _borrowPositionStorage ();
469- Position memory position = MORPHO.position (_storage.marketId , address (this ));
470- Market memory market = MORPHO.market (_storage.marketId );
468+ Id _marketId = _borrowPositionStorage ().marketId ;
469+ Position memory position = MORPHO.position (_marketId , address (this ));
470+ Market memory market = MORPHO.market (_marketId );
471471 debtValue =
472472 uint256 (position.borrowShares).toAssetsUp (uint256 (market.totalBorrowAssets), uint256 (market.totalBorrowShares));
473473 collateralValue = uint256 (position.collateral).mulDiv (price, ORACLE_PRICE_SCALE);
474474 }
475475
476476 /// @dev Reverts {LibBorrowErrors.LtvNotReduced} unless the settled position's LTV is strictly
477477 /// below the entry frame's: `exitDebt / exitCollateral < entryDebt / entryCollateral`,
478- /// compared exactly by cross-multiplication (`fullMulDiv` floors, so
479- /// `floor(a / d) < y <=> a < y * d` makes the comparison exact). `entryDebtValue > 0` in
480- /// the band (the position has debt, rounded up).
478+ /// cross-multiplied to `exitDebt * entryCollateral < entryDebt * exitCollateral` so the
479+ /// comparison is exact. The product goes through `fullMulDiv`'s 512-bit intermediate
480+ /// rather than a plain multiplication: the collateral values are price-scaled (`price` is
481+ /// an unbounded oracle value at 1e36 scale) and can exceed 128 bits, so the direct product
482+ /// can overflow 256 bits and checked multiplication would turn a legitimate fill into a
483+ /// spurious revert. Flooring keeps it exact (`floor(a * b / d) >= c <=> a * b >= c * d`
484+ /// for integers). `entryDebtValue > 0` in the band (the position has debt, rounded up).
481485 function _checkLtvReduced (uint256 price , uint256 entryDebtValue , uint256 entryCollateralValue ) internal view {
482486 (uint256 exitDebtValue , uint256 exitCollateralValue ) = _positionLtvFrame (price);
483487 if (FixedPointMathLib.fullMulDiv (exitDebtValue, entryCollateralValue, entryDebtValue) >= exitCollateralValue) {
@@ -493,9 +497,9 @@ contract MorphoBorrowPosition is IBorrowPosition, IBorrowOffers, Initializable,
493497 internal
494498 returns (uint256 totalSeized , uint256 totalDebtShares )
495499 {
496- BorrowPositionStorage storage _storage = _borrowPositionStorage ();
497- Position memory position = MORPHO.position (_storage.marketId , borrower);
498- Market memory market = MORPHO.market (_storage.marketId );
500+ Id _marketId = _borrowPositionStorage ().marketId ;
501+ Position memory position = MORPHO.position (_marketId , borrower);
502+ Market memory market = MORPHO.market (_marketId );
499503
500504 LibBorrowOffers.BorrowOffersStorage storage o = LibBorrowOffers.borrowOffersStorage ();
501505 LibBorrowOffers.ConsumeInput memory input = LibBorrowOffers.ConsumeInput ({
@@ -960,25 +964,17 @@ contract MorphoBorrowPosition is IBorrowPosition, IBorrowOffers, Initializable,
960964 /// @inheritdoc IBorrowOffers
961965 /// @dev Authorization is per offer: the recorded proposer may revoke its own offer at any time
962966 /// (a mistaken offer must not stand just because no guardian reacted before `activeAt`);
963- /// any other caller must pass {IBorrowOffersRegistry.checkCanRevokeOffer} (a guardian, or
964- /// the registry owner), the veto inside the timelock window and the kill switch for a bad
965- /// standing offer at any later stage. Liveness is checked before the proposer read so an
966- /// unknown id reverts {LibBorrowErrors.OfferNotFound} for every caller; an empty batch
967- /// performs no authorization check and is a no-op.
967+ /// any other caller needs the registry's revoke power
968+ /// ({IBorrowOffersRegistry.canRevokeOffer}: a guardian, or the registry owner), read once
969+ /// for the whole batch and enforced per offer inside {LibBorrowOffers.removeOffer}. An
970+ /// empty batch is a no-op.
968971 function revokeOffers (uint8 [] calldata ids ) external override {
969972 LibBorrowOffers.BorrowOffersStorage storage o = LibBorrowOffers.borrowOffersStorage ();
970- bool checkedCanRevokeAny; // the registry guardian/owner check passes at most once
971- // Cache the calldata length once, and each id into a stack variable, so the loop performs a
972- // single CALLDATALOAD per iteration instead of re-reading `ids.length` and `ids[i]` twice each.
973+ bool isGuardian = OFFERS_REGISTRY.canRevokeOffer (msg .sender );
973974 uint256 length = ids.length ;
974975 for (uint256 i; i < length; ++ i) {
975976 uint8 id = ids[i];
976- if (! o.isLive (id)) revert LibBorrowErrors.OfferNotFound ();
977- if (o.slab[id].proposer != msg .sender && ! checkedCanRevokeAny) {
978- OFFERS_REGISTRY.checkCanRevokeOffer (msg .sender );
979- checkedCanRevokeAny = true ;
980- }
981- o.removeOffer (id);
977+ o.removeOffer (id, msg .sender , isGuardian);
982978 emit OfferRevoked (id, msg .sender );
983979 }
984980 }
0 commit comments