@@ -118,8 +118,12 @@ abstract contract V3DexAdapter is
118118 /// that vector. 0 disables the gate. Default INITIAL_RANGE_BPS (1%).
119119 uint256 public maxSpotDeviationBps;
120120
121+ /// @dev Max |live center rate − BOT expectedCenterRate| deviation on rebalance (BPS; 0 = off). Guards
122+ /// the range anchor against a build↔exec rate anomaly the fair-NAV loss caps can't see.
123+ uint256 public maxCenterRateDeviationBps;
124+
121125 /// @dev Reserved storage for future base variables (keep subclass storage stable on upgrade).
122- uint256 [45 ] private __gap;
126+ uint256 [44 ] private __gap;
123127
124128 /* ───────────────────────────── events ───────────────────────────── */
125129
@@ -133,6 +137,7 @@ abstract contract V3DexAdapter is
133137 event SwapPairWhitelistSet (address indexed swapPair , bool status );
134138 event MaxSwapLossBpChanged (uint256 maxSwapLossBp );
135139 event MaxSpotDeviationBpsChanged (uint256 maxSpotDeviationBps );
140+ event MaxCenterRateDeviationBpsChanged (uint256 maxCenterRateDeviationBps );
136141 event CompoundSkippedSpotDeviated (uint160 spotSqrtPriceX96 , uint160 fairSqrtPriceX96 );
137142 event CompoundSkippedNoLiquidity (uint256 idleToken0 , uint256 idleToken1 );
138143 event IdleCredited (uint256 amount0 , uint256 amount1 );
@@ -161,6 +166,7 @@ abstract contract V3DexAdapter is
161166 error InsufficientBalance ();
162167 error InsufficientAmount ();
163168 error SpotDeviationTooHigh ();
169+ error CenterRateDeviationTooHigh ();
164170 error ZeroAmount ();
165171
166172 /* ─────────────────────────── constructor ────────────────────────── */
@@ -383,6 +389,14 @@ abstract contract V3DexAdapter is
383389 emit MaxSpotDeviationBpsChanged (_maxSpotDeviationBps);
384390 }
385391
392+ /// @notice Max relative deviation (BPS) allowed between the live center rate and the BOT-supplied
393+ /// expectedCenterRate on rebalance. 0 disables the guard (default).
394+ function setMaxCenterRateDeviationBps (uint256 _maxCenterRateDeviationBps ) external onlyRole (MANAGER) {
395+ if (_maxCenterRateDeviationBps > BPS) revert InvalidThreshold ();
396+ maxCenterRateDeviationBps = _maxCenterRateDeviationBps;
397+ emit MaxCenterRateDeviationBpsChanged (_maxCenterRateDeviationBps);
398+ }
399+
386400 /// @notice Whitelist (or remove) a swap venue the rebalance inventory conversion may call. Backend-built
387401 /// calldata can only target whitelisted venues.
388402 /// @dev Defense-in-depth: a swap venue must never be a token / pool / NPM the adapter holds or trusts,
@@ -410,6 +424,7 @@ abstract contract V3DexAdapter is
410424 uint256 minAmount1 ,
411425 uint256 minLiquidity ,
412426 uint160 targetSqrtPriceX96 ,
427+ uint256 expectedCenterRate ,
413428 uint256 deadline ,
414429 bytes calldata swapData
415430 ) external onlyProvider nonReentrant {
@@ -421,6 +436,14 @@ abstract contract V3DexAdapter is
421436 bool rateImplied = centerRate != 0 ;
422437 if (rateImplied) _requireCenterRateDeviation (centerRate);
423438
439+ // Assert the live rate still matches the BOT's expectedCenterRate; 0 keeps prior behavior.
440+ if (rateImplied && expectedCenterRate != 0 && maxCenterRateDeviationBps != 0 ) {
441+ uint256 delta = centerRate > expectedCenterRate
442+ ? centerRate - expectedCenterRate
443+ : expectedCenterRate - centerRate;
444+ if ((delta * BPS) / expectedCenterRate > maxCenterRateDeviationBps) revert CenterRateDeviationTooHigh ();
445+ }
446+
424447 (int24 newTickLower , int24 newTickUpper ) = _initialTickRange (centerRate);
425448 int24 oldTickLower = tickLower;
426449 int24 oldTickUpper = tickUpper;
@@ -639,6 +662,11 @@ abstract contract V3DexAdapter is
639662 (sqrtPriceX96, ) = IV3PoolMinimal (POOL).slot0 ();
640663 }
641664
665+ /// @inheritdoc IV3DexAdapter
666+ function centerRate () external view returns (uint256 ) {
667+ return _lstNativeRate ();
668+ }
669+
642670 /// @dev True when the pool spot price is within `maxSpotDeviationBps` (bps of price) of the fair price.
643671 /// Prices are compared in Q96 space (price = sqrtPriceX96^2 / 2^96) via FullMath, overflow-safe.
644672 /// `maxSpotDeviationBps == 0` disables the gate. `fair` is rate-anchored (flash-loan-immune for the
0 commit comments