@@ -54,6 +54,13 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
5454 /// @dev Whitelisted V3Provider contracts (collateral token = the provider itself).
5555 mapping (address => bool ) public v3Providers;
5656
57+ /// @dev Pass-back of the leg amounts redeemed inside onMoolahLiquidate, so flashLiquidate can report
58+ /// the actual redeemed amount0 / amount1 in its V3Liquidation event rather than 0. Appended at
59+ /// the end of the storage layout (upgrade-safe). flashLiquidate zeroes them before each liquidate
60+ /// so a non-redeeming flash liquidation — or a stale value from an earlier call — reports 0.
61+ uint256 private _redeemedAmount0;
62+ uint256 private _redeemedAmount1;
63+
5764 /* ──────────────────────────── events ────────────────────────────── */
5865
5966 event TokenWhitelistChanged (address indexed token , bool status );
@@ -276,6 +283,11 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
276283 address effectiveToken0Spender = params.token0Spender == address (0 ) ? params.token0Pair : params.token0Spender;
277284 address effectiveToken1Spender = params.token1Spender == address (0 ) ? params.token1Pair : params.token1Spender;
278285
286+ // Clear any stale pass-back (e.g. from an earlier flashLiquidate in the same tx). The callback
287+ // fills these in only when it redeems; a non-redeeming flash liquidation reports 0 / 0.
288+ _redeemedAmount0 = 0 ;
289+ _redeemedAmount1 = 0 ;
290+
279291 (uint256 _seized , uint256 _repaid ) = IMoolah (MOOLAH).liquidate (
280292 mp,
281293 borrower,
@@ -301,7 +313,7 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
301313 )
302314 );
303315
304- emit V3Liquidation (id, params.v3Provider, borrower, _seized, _repaid, 0 , 0 );
316+ emit V3Liquidation (id, params.v3Provider, borrower, _seized, _repaid, _redeemedAmount0, _redeemedAmount1 );
305317 }
306318
307319 /**
@@ -401,6 +413,10 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
401413 // leg is an ERC-20 sold via approve + call. Read it from the provider — never hardcode a chain.
402414 address wrappedNative = IV3Provider (d.v3Provider).WRAPPED_NATIVE ();
403415
416+ // Snapshot before redeeming/swapping so profitability is judged on THIS liquidation's
417+ // delta, not masked by loanToken balance already sitting idle from prior liquidations.
418+ uint256 before = d.loanToken.balanceOf (address (this ));
419+
404420 // Redeem V3 shares → TOKEN0 + TOKEN1; the wrapped-native leg arrives as the native coin.
405421 (uint256 amount0 , uint256 amount1 ) = IV3Provider (d.v3Provider).redeemShares (
406422 d.seized,
@@ -409,6 +425,10 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
409425 address (this )
410426 );
411427
428+ // Pass the actual redeemed amounts back to flashLiquidate for the V3Liquidation event.
429+ _redeemedAmount0 = amount0;
430+ _redeemedAmount1 = amount1;
431+
412432 // Swap TOKEN0 → loanToken (skip if already loanToken or no swap requested).
413433 if (d.swapToken0 && amount0 > 0 && token0 != d.loanToken) {
414434 _swapRedeemedLeg (token0 == wrappedNative, d.token0Pair, d.token0Spender, token0, amount0, d.swapToken0Data);
@@ -427,7 +447,8 @@ contract V3Liquidator is ReentrancyGuardUpgradeable, UUPSUpgradeable, AccessCont
427447 if (nativeBalance > 0 ) IWBNB (wrappedNative).deposit { value: nativeBalance }();
428448 }
429449
430- if (d.loanToken.balanceOf (address (this )) < repaidAssets) revert NoProfit ();
450+ uint256 out = d.loanToken.balanceOf (address (this )) - before;
451+ if (out < repaidAssets) revert NoProfit ();
431452 }
432453
433454 // Approve Moolah to pull the repayment (always done, flash or pre-funded).
0 commit comments