From cb2ba62693c30c79a69222a3ebaa61763494215e Mon Sep 17 00:00:00 2001 From: ffulb Date: Fri, 3 Apr 2026 05:43:49 +0000 Subject: [PATCH] fix: prevent near-zero division DoS in SpreadCloseSwapService When closing the last swap in a linked list (nextSwapId == 0), the time-weighted notional is recalculated with division by (tenorInSeconds - swapTimePast). If swapTimePast approaches tenorInSeconds, the denominator approaches 1, causing the result to overflow uint96 in SpreadStorageLibs.saveTimeWeightedNotionalForAssetAndTenor(). This permanently bricks the spread storage for the affected asset/tenor pair, blocking all subsequent swap operations. Fix: enforce a minimum denominator of 1% of the tenor length. --- contracts/amm/spread/SpreadCloseSwapService.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contracts/amm/spread/SpreadCloseSwapService.sol b/contracts/amm/spread/SpreadCloseSwapService.sol index 7de46a1cf..b6e1117a4 100644 --- a/contracts/amm/spread/SpreadCloseSwapService.sol +++ b/contracts/amm/spread/SpreadCloseSwapService.sol @@ -79,16 +79,26 @@ contract SpreadCloseSwapService is ISpreadCloseSwapService { actualTimeWeightedNotionalToSave = 0; swapTimePast = 0; } + // @dev Ensure denominator is at least 1% of tenor to prevent near-zero division + // that could cause uint96 overflow in SpreadStorageLibs.saveTimeWeightedNotionalForAssetAndTenor + uint256 denominator = tenorInSeconds - swapTimePast; + uint256 minDenominator = tenorInSeconds / 100; + if (minDenominator == 0) { + minDenominator = 1; + } + if (denominator < minDenominator) { + denominator = minDenominator; + } if (direction == 0) { timeWeightedNotional.lastUpdateTimePayFixed = lastOpenSwap.openSwapTimestamp; timeWeightedNotional.timeWeightedNotionalPayFixed = (actualTimeWeightedNotionalToSave * tenorInSeconds) / - (tenorInSeconds - swapTimePast); + denominator; } else { timeWeightedNotional.lastUpdateTimeReceiveFixed = lastOpenSwap.openSwapTimestamp; timeWeightedNotional.timeWeightedNotionalReceiveFixed = (actualTimeWeightedNotionalToSave * tenorInSeconds) / - (tenorInSeconds - swapTimePast); + denominator; } } else { if (direction == 0) {