diff --git a/onchain/script/backtesting/EventReplayer.sol b/onchain/script/backtesting/EventReplayer.sol index 75d7b16..ecb36e8 100644 --- a/onchain/script/backtesting/EventReplayer.sol +++ b/onchain/script/backtesting/EventReplayer.sol @@ -135,10 +135,14 @@ contract EventReplayer is IUniswapV3MintCallback, IUniswapV3SwapCallback { } } - // Final drift sample: capture any trailing events after the last checkpoint. - // This prevents understating drift when (totalReplayed % LOG_INTERVAL) != 0. - if (hasSwapRef) { - (uint160 finalSqrtPrice, int24 finalTick,,,,,) = pool.slot0(); + // Fetch final pool state once — used both for the trailing drift sample and _logSummary. + (uint160 finalSqrtPrice, int24 finalTick,,,,,) = pool.slot0(); + + // Final drift sample: captures trailing events after the last checkpoint. + // Guard: when idx is an exact multiple of LOG_INTERVAL, _logCheckpoint already fired for + // this identical pool state inside the loop — accumulating stats again would double-count + // that measurement in totalAbsDrift and checkpointsWithDrift. + if (hasSwapRef && idx % LOG_INTERVAL != 0) { int256 diff = int256(finalTick) - int256(lastExpectedTick); uint256 absDrift = diff >= 0 ? uint256(diff) : uint256(-diff); totalAbsDrift += absDrift; @@ -155,7 +159,7 @@ contract EventReplayer is IUniswapV3MintCallback, IUniswapV3SwapCallback { } } - _logSummary(idx); + _logSummary(idx, finalSqrtPrice, finalTick); } // ------------------------------------------------------------------------- @@ -410,8 +414,7 @@ contract EventReplayer is IUniswapV3MintCallback, IUniswapV3SwapCallback { return keccak256(bytes(a)) == keccak256(bytes(b)); } - function _logSummary(uint256 totalReplayed) internal view { - (uint160 finalSp, int24 finalTick,,,,,) = pool.slot0(); + function _logSummary(uint256 totalReplayed, uint160 finalSp, int24 finalTick) internal view { console2.log("=== Replay Complete ==="); console2.log("Total events: ", totalReplayed); console2.log("Skipped: ", skippedCount);