Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/upgrade-safety.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ jobs:
- name: Install upgrade-guard
run: |
cargo install --git https://github.com/razww/upgrade-guard \
--tag v0.1.1 upgrade-guard --locked
--tag v0.1.2 upgrade-guard --locked

# Faster alternative: skip the Rust build, download the prebuilt binary.
# - run: |
# gh release download v0.1.1 --repo razww/upgrade-guard \
# gh release download v0.1.2 --repo razww/upgrade-guard \
# --pattern 'upgrade-guard-x86_64-linux*' --dir /tmp/ug
# ( cd /tmp/ug && sha256sum -c upgrade-guard-x86_64-linux.sha256 )
# install -m0755 /tmp/ug/upgrade-guard-x86_64-linux /usr/local/bin/upgrade-guard
Expand Down
16 changes: 14 additions & 2 deletions src/provider/interfaces/IV3DexAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ interface IV3DexAdapter {
/// @notice Current pool spot price (slot0).
function spotSqrtPriceX96() external view returns (uint160);

/// @notice Live LST↔native center rate the next rebalance will use (0 for pure-TWAP pairs).
function centerRate() external view returns (uint256);

/// @notice Simulate adding `amount0Desired/amount1Desired` at the current spot price.
function previewAddLiquidity(
uint256 amount0Desired,
Expand Down Expand Up @@ -147,13 +150,22 @@ interface IV3DexAdapter {
/// @notice Set the spot-vs-fair deviation gate (onlyRole MANAGER; bps of price, 0 disables).
function setMaxSpotDeviationBps(uint256 maxSpotDeviationBps) external;

/// @notice Recenter the position to its range and convert inventory to the optimal ratio.
/// onlyProvider — the provider gates the caller with the BOT role.
/// @notice Max |live center rate − BOT-supplied expectedCenterRate| deviation (bps) tolerated on
/// rebalance (0 disables the guard).
function maxCenterRateDeviationBps() external view returns (uint256);

/// @notice Set the center-rate deviation gate (onlyRole MANAGER; bps, 0 disables).
function setMaxCenterRateDeviationBps(uint256 maxCenterRateDeviationBps) external;

/// @notice Recenter the position and convert inventory to the optimal ratio. onlyProvider (the provider
/// gates on BOT). `expectedCenterRate` = the live rate the BOT built against (0 = skip); reverts
/// if it deviates from the on-chain rate by more than `maxCenterRateDeviationBps`.
function rebalance(
uint256 minAmount0,
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) external;
Expand Down
3 changes: 2 additions & 1 deletion src/provider/v3/SlisBNBV3Provider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ contract SlisBNBV3Provider is V3Provider {
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) external onlyRole(BOT) nonReentrant {
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, deadline, swapData);
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, expectedCenterRate, deadline, swapData);
}

/* ─────────────────── slisBNBx: sync / view ──────────────────────── */
Expand Down
30 changes: 29 additions & 1 deletion src/provider/v3/V3DexAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ abstract contract V3DexAdapter is
/// that vector. 0 disables the gate. Default INITIAL_RANGE_BPS (1%).
uint256 public maxSpotDeviationBps;

/// @dev Max |live center rate − BOT expectedCenterRate| deviation on rebalance (BPS; 0 = off). Guards
/// the range anchor against a build↔exec rate anomaly the fair-NAV loss caps can't see.
uint256 public maxCenterRateDeviationBps;

/// @dev Reserved storage for future base variables (keep subclass storage stable on upgrade).
uint256[45] private __gap;
uint256[44] private __gap;

/* ───────────────────────────── events ───────────────────────────── */

Expand All @@ -133,6 +137,7 @@ abstract contract V3DexAdapter is
event SwapPairWhitelistSet(address indexed swapPair, bool status);
event MaxSwapLossBpChanged(uint256 maxSwapLossBp);
event MaxSpotDeviationBpsChanged(uint256 maxSpotDeviationBps);
event MaxCenterRateDeviationBpsChanged(uint256 maxCenterRateDeviationBps);
event CompoundSkippedSpotDeviated(uint160 spotSqrtPriceX96, uint160 fairSqrtPriceX96);
event CompoundSkippedNoLiquidity(uint256 idleToken0, uint256 idleToken1);
event IdleCredited(uint256 amount0, uint256 amount1);
Expand Down Expand Up @@ -161,6 +166,7 @@ abstract contract V3DexAdapter is
error InsufficientBalance();
error InsufficientAmount();
error SpotDeviationTooHigh();
error CenterRateDeviationTooHigh();
error ZeroAmount();

/* ─────────────────────────── constructor ────────────────────────── */
Expand Down Expand Up @@ -383,6 +389,14 @@ abstract contract V3DexAdapter is
emit MaxSpotDeviationBpsChanged(_maxSpotDeviationBps);
}

/// @notice Max relative deviation (BPS) allowed between the live center rate and the BOT-supplied
/// expectedCenterRate on rebalance. 0 disables the guard (default).
function setMaxCenterRateDeviationBps(uint256 _maxCenterRateDeviationBps) external onlyRole(MANAGER) {
if (_maxCenterRateDeviationBps > BPS) revert InvalidThreshold();
maxCenterRateDeviationBps = _maxCenterRateDeviationBps;
emit MaxCenterRateDeviationBpsChanged(_maxCenterRateDeviationBps);
}

/// @notice Whitelist (or remove) a swap venue the rebalance inventory conversion may call. Backend-built
/// calldata can only target whitelisted venues.
/// @dev Defense-in-depth: a swap venue must never be a token / pool / NPM the adapter holds or trusts,
Expand Down Expand Up @@ -410,6 +424,7 @@ abstract contract V3DexAdapter is
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) external onlyProvider nonReentrant {
Expand All @@ -421,6 +436,14 @@ abstract contract V3DexAdapter is
bool rateImplied = centerRate != 0;
if (rateImplied) _requireCenterRateDeviation(centerRate);

// Assert the live rate still matches the BOT's expectedCenterRate; 0 keeps prior behavior.
if (rateImplied && expectedCenterRate != 0 && maxCenterRateDeviationBps != 0) {
uint256 delta = centerRate > expectedCenterRate
? centerRate - expectedCenterRate
: expectedCenterRate - centerRate;
if ((delta * BPS) / expectedCenterRate > maxCenterRateDeviationBps) revert CenterRateDeviationTooHigh();
}

(int24 newTickLower, int24 newTickUpper) = _initialTickRange(centerRate);
int24 oldTickLower = tickLower;
int24 oldTickUpper = tickUpper;
Expand Down Expand Up @@ -639,6 +662,11 @@ abstract contract V3DexAdapter is
(sqrtPriceX96, ) = IV3PoolMinimal(POOL).slot0();
}

/// @inheritdoc IV3DexAdapter
function centerRate() external view returns (uint256) {
return _lstNativeRate();
}

/// @dev True when the pool spot price is within `maxSpotDeviationBps` (bps of price) of the fair price.
/// Prices are compared in Q96 space (price = sqrtPriceX96^2 / 2^96) via FullMath, overflow-safe.
/// `maxSpotDeviationBps == 0` disables the gate. `fair` is rate-anchored (flash-loan-immune for the
Expand Down
11 changes: 10 additions & 1 deletion src/provider/v3/V3Provider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -705,13 +705,22 @@ abstract contract V3Provider is
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) internal {
uint256 navBefore = _positionValueUsd();
if (totalSupply() != 0 && navBefore == 0) revert OracleZero();

IV3DexAdapter(ADAPTER).rebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, deadline, swapData);
IV3DexAdapter(ADAPTER).rebalance(
minAmount0,
minAmount1,
minLiquidity,
targetSqrtPriceX96,
expectedCenterRate,
deadline,
swapData
);

uint256 navAfter = _positionValueUsd();

Expand Down
3 changes: 2 additions & 1 deletion src/provider/v3/WbETHV3Provider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ contract WbETHV3Provider is V3Provider {
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) external onlyRole(BOT) nonReentrant {
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, deadline, swapData);
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, expectedCenterRate, deadline, swapData);
}
}
3 changes: 2 additions & 1 deletion src/provider/v3/WstETHV3Provider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ contract WstETHV3Provider is V3Provider {
uint256 minAmount1,
uint256 minLiquidity,
uint160 targetSqrtPriceX96,
uint256 expectedCenterRate,
uint256 deadline,
bytes calldata swapData
) external onlyRole(BOT) nonReentrant {
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, deadline, swapData);
_guardedRebalance(minAmount0, minAmount1, minLiquidity, targetSqrtPriceX96, expectedCenterRate, deadline, swapData);
}
}
Loading
Loading