Farseen0/scar-pairs huggingface dataset:
LOW severity: Missing `poolStatus` check in `bypassQueue`. **Description:** The `bypassQueue` function in `PriorityPool.sol` doesn't check the pool's status before depositing tokens directly into the staking pool.

The pool status check is part of the protocol's emergency response system. The...

Vulnerable code:
function bypassQueue(
    address _account,
    uint256 _amount,
    bytes[] calldata _data
) external onlyQueueBypassController {
    token.safeTransferFrom(msg.sender, address(this), _amount);

    uint256 canDeposit = stakingPool.canDeposit();
    if (canDeposit < _amount) revert InsufficientDepositRoom();

    stakingPool.deposit(_account, _amount, _data);
}

@>     * @notice Reopens the priority pool and security pool after they were paused as a result
@>     * of a loss and updates strategy rewards in the staking pool
     * @param _data encoded data to pass to strategies
     */
    function reopenPool(bytes calldata _data) external onlyOwner {
        if (priorityPool.poolStatus() == IPriorityPool.PoolStatus.OPEN) revert PoolOpen();


        priorityPool.setPoolStatus(IPriorityPool.PoolStatus.OPEN);
        if (address(securityPool) != address(0) && securityPool.claimInProgress()) {
            securityPool.resolveClaim();
        }
        _updateRewards(_data);
    }

function bypassQueue(
    address _account,
    uint256 _amount,
    bytes[] calldata _data
) external onlyQueueBypassController {
+    if (poolStatus != PoolStatus.OPEN) revert DepositsDisabled();
    ...
}
LOW severity: Existing Chainlink stakers can skip queue by bypassing migration requirements. **Description:** The purpose of `LINKMigrator` is that users can migrate their existing position from the Chainlink community pool to stake.link vaults, even when the community pool is at full utilization, since vacating a position frees up space. For users without an existing position, a queueing.....

Vulnerable code:
function initiateMigration(uint256 _amount) external {
    if (_amount == 0) revert InvalidAmount();

    uint256 principal = communityPool.getStakerPrincipal(msg.sender);

    if (principal < _amount) revert InsufficientAmountStaked();
    if (!_isUnbonded(msg.sender)) revert TokensNotUnbonded();

    migrations[msg.sender] = Migration(
        uint128(principal),
        uint128(_amount),
        uint64(block.timestamp)
    );
}

uint256 amountWithdrawn = migration.principalAmount -
    communityPool.getStakerPrincipal(_sender);
if (amountWithdrawn < _value) revert InsufficientTokensWithdrawn();

it('can bypass queue using existing position', async () => {
  const { migrator, communityPool, accounts, token, stakingPool } = await loadFixture(
    deployFixture
  )

  // increase max pool size so we have space for the extra position
  await communityPool.setMaxPoolSize(toEther(3000))

  // deploy our small contract to hold the existing position
  const chainlinkPosition = (await deploy('ChainlinkPosition', [
    communityPool.target,
    token.target,
  ])) as ChainlinkPosition

  // get to claim period
  await communityPool.unbond()
  await time.increase(unbondingPeriod)

  // start batch transaction
  await ethers.provider.send('evm_setAutomine', [false])

  // 1. call initiate migration
  await migrator.initiateMigration(toEther(1000))

  // 2. unstake
  await communityPool.unstake(toEther(1000))

  // 3. transfer the existing position to a contract you control
  await token.transfer(chainlinkPosition.target, toEther(1000))
  await chainlinkPosition.deposit()

  // 4. transferAndCall a new position bypassing the queue
  await token.transferAndCall(
    migrator.target,
    toEther(1000),
    ethers.AbiCoder.defaultAbiCoder().encode(['bytes[]'], [[encodeVaults([])]])
  )
  await ethers.provider.send('evm_mine')
  await ethers.provider.send('evm_setAutomine', [true])

  // user has both a 1000 LINK position in stake.link StakingPool and chainlink community pool
  assert.equal(fromEther(await communityPool.getStakerPrincipal(accounts[0])), 0)
  assert.equal(fromEther(await stakingPool.balanceOf(accounts[0])), 1000)
  assert.equal(fromEther(await communityPool.getStakerPrincipal(chainlinkPosition.target)), 1000)

  // community pool is full again
  assert.equal(fromEther(await communityPool.getTotalPrincipal()), 3000)
  assert.equal(fromEther(await stakingPool.totalStaked()), 2000)
  assert.deepEqual(await migrator.migrations(accounts[0]), [0n, 0n, 0n])
})
HIGH severity: Consider renaming `LINKMigrator::_isUnbonded` for clarity. **Description:** In the `LINKMigrator` contract, the function [`_isUnbonded`](https://github.com/stakedotlink/contracts/blob/0bd5e1eecd866b2077d6887e922c4c5940a6b452/contracts/linkStaking/LINKMigrator.sol#L132-L137) checks whether a user is currently within the claim period for Chainlink...

Vulnerable code:
function _isUnbonded(address _account) private view returns (bool) {
    uint256 unbondingPeriodEndsAt = communityPool.getUnbondingEndsAt(_account);
    if (unbondingPeriodEndsAt == 0 || block.timestamp < unbondingPeriodEndsAt) return false;

    return block.timestamp <= communityPool.getClaimPeriodEndsAt(_account);
}

function _inClaimPeriod(Staker storage staker) private view returns (bool) {
  if (staker.unbondingPeriodEndsAt == 0 || block.timestamp < staker.unbondingPeriodEndsAt) {
    return false;
  }

  return block.timestamp <= staker.claimPeriodEndsAt;
}
HIGH severity: Inefficient storage layout in `LINKMigrator.Migration`. **Description:** Here's the [`LINKMigrator.Migration`](https://github.com/stakedotlink/contracts/blob/0bd5e1eecd866b2077d6887e922c4c5940a6b452/contracts/linkStaking/LINKMigrator.sol#L31-L38) struct:

This struct occupies more than 256 bits and therefore spans two storage slots. However, any LINK...

Vulnerable code:
struct Migration {
    // amount of principal staked in Chainlink community pool
    uint128 principalAmount;
    // amount to migrate
    uint128 amount;
    // timestamp when migration was initiated
    uint64 timestamp;
}
HIGH severity: Pools configured with a malicious hook can bypass the `BunniHub` re-entrancy guard to drain all raw balances and vault reserves of legitimate pools. **Description:** The `BunniHub` is the main contract that both holds and accounts the raw balances and  vault reserves that each pool has deposited. It inherits `ReentrancyGuard` which implements the `nonReentrant` modifier:

During the rebalance of a Bunni pool, it is intended to separate this...

Vulnerable code:
modifier nonReentrant() {
    _nonReentrantBefore();
    _;
    _nonReentrantAfter();
}

function _nonReentrantBefore() internal {
    uint256 statusSlot = STATUS_SLOT;
    uint256 status;
    /// @solidity memory-safe-assembly
    assembly {
        status := tload(statusSlot)
    }
    if (status == ENTERED) revert ReentrancyGuard__ReentrantCall();

    uint256 entered = ENTERED;
    /// @solidity memory-safe-assembly
    assembly {
        tstore(statusSlot, entered)
    }
}

function _nonReentrantAfter() internal {
    uint256 statusSlot = STATUS_SLOT;
    uint256 notEntered = NOT_ENTERED;
    /// @solidity memory-safe-assembly
    assembly {
        tstore(statusSlot, notEntered)
    }
}

function lockForRebalance(PoolKey calldata key) external notPaused(6) {
    if (address(_getBunniTokenOfPool(key.toId())) == address(0)) revert BunniHub__BunniTokenNotInitialized();
    if (msg.sender != address(key.hooks)) revert BunniHub__Unauthorized();
    _nonReentrantBefore();
}

function unlockForRebalance(PoolKey calldata key) external notPaused(7) {
    if (address(_getBunniTokenOfPool(key.toId())) == address(0)) revert BunniHub__BunniTokenNotInitialized();
    if (msg.sender != address(key.hooks)) revert BunniHub__Unauthorized();
    _nonReentrantAfter();
}

function deployBunniToken(HubStorage storage s, Env calldata env, IBunniHub.DeployBunniTokenParams calldata params)
    external
    returns (IBunniToken token, PoolKey memory key)
{
    ...

    // ensure hook params are valid
    if (address(params.hooks) == address(0)) revert BunniHub__HookCannotBeZero();
    if (!params.hooks.isValidParams(params.hookParams)) revert BunniHub__InvalidHookParams();

    ...
}
MEDIUM severity: Hooklet token transfer hooks will reference the incorrect sender due to missing use of `LibMulticaller.senderOrSigner()`. **Description:** `LibMulticaller` is used throughout the codebase to retrieve the actual sender of multicall transactions; however, `BunniToken::_beforeTokenTransfer` and `BunniToken::_afterTokenTransfer` both incorrectly pass `msg.sender` directly to the corresponding Hooklet...

Vulnerable code:
function _beforeTokenTransfer(address from, address to, uint256 amount, address newReferrer) internal override {
    IHooklet hooklet_ = hooklet();
    if (hooklet_.hasPermission(HookletLib.BEFORE_TRANSFER_FLAG)) {
        hooklet_.hookletBeforeTransfer(msg.sender, poolKey(), this, from, to, amount);
    }
}

function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
    // call hooklet
    IHooklet hooklet_ = hooklet();
    if (hooklet_.hasPermission(HookletLib.AFTER_TRANSFER_FLAG)) {
        hooklet_.hookletAfterTransfer(msg.sender, poolKey(), this, from, to, amount);
    }
}

function _beforeTokenTransfer(address from, address to, uint256 amount, address newReferrer) internal override {
    IHooklet hooklet_ = hooklet();
    if (hooklet_.hasPermission(HookletLib.BEFORE_TRANSFER_FLAG)) {
--      hooklet_.hookletBeforeTransfer(msg.sender, poolKey(), this, from, to, amount);
++      hooklet_.hookletBeforeTransfer(LibMulticaller.senderOrSigner(), poolKey(), this, from, to, amount);
    }
}

function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
    // call hooklet
    IHooklet hooklet_ = hooklet();
    if (hooklet_.hasPermission(HookletLib.AFTER_TRANSFER_FLAG)) {
--      hooklet_.hookletAfterTransfer(msg.sender, poolKey(), this, from, to, amount);
++      hooklet_.hookletAfterTransfer(LibMulticaller.senderOrSigner(), poolKey(), this, from, to, amount);
    }
}
MEDIUM severity: Broken block time assumptions affect am-AMM epoch duration and can DoS rebalancing on Arbitrum. **Description:** `AmAmm` defines the following virtual function which specifies the auction delay parameter in blocks:

Here, `7200` assumes a 12s Ethereum mainnet block time. This corresponds to 24-hour epochs and is the intended duration for all chains. Given that different chains have different.....

Vulnerable code:
function K(PoolId) internal view virtual returns (uint48) {
    return 7200;
}

uint48 internal immutable _K;

function K(PoolId) internal view virtual override returns (uint48) {
    return _K;
}

Mainnet
AMAMM_K_1=7200

Arbitrum
AMAMM_K_42161=345600

Base
AMAMM_K_8453=43200

Unichain
AMAMM_K_130=86400
MEDIUM severity: Potential cross-contract re-entrancy between `BunniHub::deposit` and `BunniToken` can corrupt Hooklet state. **Description:** Hooklets are intended to allow pool deployers to inject custom logic into various key pool operations such as initialization, deposits, withdrawals, swaps and Bunni token transfers.

To ensure that the hooklet invocations receive actual return values, all after operation hooks...

Vulnerable code:
function deposit(HubStorage storage s, Env calldata env, IBunniHub.DepositParams calldata params)
        external
        returns (uint256 shares, uint256 amount0, uint256 amount1)
    {
        ...
        // refund excess ETH
        if (params.poolKey.currency0.isAddressZero()) {
            if (address(this).balance != 0) {
@>              params.refundRecipient.safeTransferETH(
                    FixedPointMathLib.min(address(this).balance, msg.value - amount0Spent)
                );
            }
        } else if (params.poolKey.currency1.isAddressZero()) {
            if (address(this).balance != 0) {
@>              params.refundRecipient.safeTransferETH(
                    FixedPointMathLib.min(address(this).balance, msg.value - amount1Spent)
                );
            }
        }

        // emit event
        emit IBunniHub.Deposit(msgSender, params.recipient, poolId, amount0, amount1, shares);

        /// -----------------------------------------------------------------------
        /// Hooklet call
        /// -----------------------------------------------------------------------

@>      state.hooklet.hookletAfterDeposit(
            msgSender, params, IHooklet.DepositReturnData({shares: shares, amount0: amount0, amount1: amount1})
        );
    }

function _beforeTokenTransfer(address from, address to, uint256 amount, address newReferrer) internal override {
        ...
        // call hooklet
        // occurs after the referral reward accrual to prevent the hooklet from
        // messing up the accounting
        IHooklet hooklet_ = hooklet();
        if (hooklet_.hasPermission(HookletLib.BEFORE_TRANSFER_FLAG)) {
@>          hooklet_.hookletBeforeTransfer(msg.sender, poolKey(), this, from, to, amount);
        }
    }

    function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
        // call hooklet
        IHooklet hooklet_ = hooklet();
        if (hooklet_.hasPermission(HookletLib.AFTER_TRANSFER_FLAG)) {
@>          hooklet_.hookletAfterTransfer(msg.sender, poolKey(), this, from, to, amount);
        }
    }
MEDIUM severity: Inconsistent Hooklet data provisioning for rebalancing operations. **Description:** Bunni utilizes Hooklets to enable pool deployers to inject custom logic into various pool operations, allowing for the implementation of advanced strategies, customized behavior, and integration with external systems. These Hooklets can be invoked during key operations, including...

Vulnerable code:
struct DepositReturnData {
        uint256 shares;
@>      uint256 amount0;
@>      uint256 amount1;
    }

    ...

    struct WithdrawReturnData {
@>      uint256 amount0;
@>      uint256 amount1;
    }

    ...

    struct SwapReturnData {
        uint160 updatedSqrtPriceX96;
        int24 updatedTick;
@>      uint256 inputAmount;
@>      uint256 outputAmount;
        uint24 swapFee;
        uint256 totalLiquidity;
    }
MEDIUM severity: `BunniHook::beforeSwap` does not account for vault fees paid when adjusting raw token balances which could result in small losses to liquidity providers. **Description:** Unlike during pool deposits, `BunniHook::beforeSwap` does not charge an additional fee on swaps that result in input tokens being moved to/from vaults that charge a fee on such operations. The `BunniHub` handles swaps via the `hookHandleSwap()` function which checks whether tokens.....

Vulnerable code:
function _updateRawBalanceIfNeeded(
        Currency currency,
        ERC4626 vault,
        uint256 rawBalance,
        uint256 reserve,
        uint256 minRatio,
        uint256 maxRatio,
        uint256 targetRatio
    ) internal returns (uint256 newReserve, uint256 newRawBalance) {
        uint256 balance = rawBalance + getReservesInUnderlying(reserve, vault);
        uint256 minRawBalance = balance.mulDiv(minRatio, RAW_TOKEN_RATIO_BASE);
@>      uint256 maxRawBalance = balance.mulDiv(maxRatio, RAW_TOKEN_RATIO_BASE);

@>      if (rawBalance < minRawBalance || rawBalance > maxRawBalance) {
            uint256 targetRawBalance = balance.mulDiv(targetRatio, RAW_TOKEN_RATIO_BASE);
            (int256 reserveChange, int256 rawBalanceChange) =
@>              _updateVaultReserveViaClaimTokens(targetRawBalance.toInt256() - rawBalance.toInt256(), currency, vault);
            newReserve = _updateBalance(reserve, reserveChange);
            newRawBalance = _updateBalance(rawBalance, rawBalanceChange);
        } else {
            (newReserve, newRawBalance) = (reserve, rawBalance);
        }
    }

returnData.reserveAmount0 = balance0 == 0 ? 0 : returnData.amount0.mulDiv(reserveBalance0, balance0);
returnData.reserveAmount1 = balance1 == 0 ? 0 : returnData.amount1.mulDiv(reserveBalance1, balance1);
MEDIUM severity: Incorrect bond/stablecoin pair decimals assumptions in `OracleUniGeoDistribution`. **Description:** The `OracleUniGeoDistribution` intends to compute a geometric or uniform distribution with two limits; one is arbitrarily set by the owner, and the other is derived from an external price oracle. The price of the bond is returned in terms of USD in 18 decimals before it is...

Vulnerable code:
function floorPriceToRick(uint256 floorPriceWad, int24 tickSpacing) public view returns (int24 rick) {
    // convert floor price to sqrt price
    // assume bond is currency0, floor price's unit is (currency1 / currency0)
    // unscale by WAD then rescale by 2**(96*2), then take the sqrt to get sqrt(floorPrice) * 2**96
    uint160 sqrtPriceX96 = ((floorPriceWad << 192) / WAD).sqrt().toUint160();
    // convert sqrt price to rick
    rick = sqrtPriceX96.getTickAtSqrtPrice();
    rick = bondLtStablecoin ? rick : -rick; // need to invert the sqrt price if bond is currency1
    rick = roundTickSingle(rick, tickSpacing);
}

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.15;

import "./BaseTest.sol";

interface IUniswapV3PoolState {
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );
}

contract DecimalsPoC is BaseTest {
    function setUp() public override {
        super.setUp();
    }

    function test_slot0PoC() public {
        uint256 mainnetFork;
        string memory MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
        mainnetFork = vm.createFork(MAINNET_RPC_URL);
        vm.selectFork(mainnetFork);

        address DAI_WETH = 0xa80964C5bBd1A0E95777094420555fead1A26c1e;
        address USDC_WETH = 0x7BeA39867e4169DBe237d55C8242a8f2fcDcc387;

        (uint160 sqrtPriceX96DAI,,,,,,) = IUniswapV3PoolState(DAI_WETH).slot0();
        (uint160 sqrtPriceX96USDC,,,,,,) = IUniswapV3PoolState(USDC_WETH).slot0();

        console2.log("sqrtPriceX96DAI: %s", sqrtPriceX96DAI);
        console2.log("sqrtPriceX96USDC: %s", sqrtPriceX96USDC);
    }
}

Ran 1 test for test/DecimalsPoC.t.sol:DecimalsPoC
[PASS] test_slot0PoC() (gas: 20679)
Logs:
  sqrtPriceX96DAI: 1611883263726799730515701216
  sqrtPriceX96USDC: 1618353216855286506291652802704389

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.30s (2.78s CPU time)

Ran 1 test suite in 4.45s (3.30s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
HIGH severity: Potentially dirty upper bits of narrow types could affect allowance computations in `ERReferrer`. **Description:** Solidity makes no guarantees about the contents of the upper bits of variables whose types do not span the full 32-byte width. Consider `ERC20Referrer::_transfer`, for example:

Here, and similarly in `transferFrom()`, the `from_` variable defined within the assembly block shifts...

Vulnerable code:
function _transfer(address from, address to, uint256 amount) internal virtual override {
    bool toLocked;

    _beforeTokenTransfer(from, to, amount, address(0));
    /// @solidity memory-safe-assembly
    assembly {
        let from_ := shl(96, from)
        // Compute the balance slot and load its value.
        mstore(0x0c, _BALANCE_SLOT_SEED)
        mstore(0x00, from)
        let fromBalanceSlot := keccak256(0x0c, 0x20)
        let fromBalance := sload(fromBalanceSlot)
        ...
    }
    ...
}

function approve(address spender, uint256 amount) public virtual override returns (bool) {
    address msgSender = LibMulticaller.senderOrSigner();
    /// @solidity memory-safe-assembly
    assembly {
        // Compute the allowance slot and store the amount.
        mstore(0x20, spender)
        mstore(0x0c, _ALLOWANCE_SLOT_SEED)
        mstore(0x00, msgSender)
        sstore(keccak256(0x0c, 0x34), amount)
        // Emit the {Approval} event.
        mstore(0x00, amount)
        log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, msgSender, shr(96, mload(0x2c)))
    }
    return true;
}
LOW severity: `LibBuyTheDipGeometricDistribution::cumulativeAmount0` will always return 0 due to insufficient `alphaX96` validation. **Description:** While the combination of `LibBuyTheDipGeometricDistribution::isValidParams` and `LibBuyTheDipGeometricDistribution::geometricIsValidParams` should be functionally equivalent to `LibGeometricDistribution::isValidParams`, except for the shift mode enforcement, this is not the...

Vulnerable code:
function geometricIsValidParams(int24 tickSpacing, bytes32 ldfParams) internal pure returns (bool) {
        (int24 minUsableTick, int24 maxUsableTick) =
            (TickMath.minUsableTick(tickSpacing), TickMath.maxUsableTick(tickSpacing));

        // | shiftMode - 1 byte | minTickOrOffset - 3 bytes | length - 2 bytes | alpha - 4 bytes |
        uint8 shiftMode = uint8(bytes1(ldfParams));
        int24 minTickOrOffset = int24(uint24(bytes3(ldfParams << 8)));
        int24 length = int24(int16(uint16(bytes2(ldfParams << 32))));
        uint256 alpha = uint32(bytes4(ldfParams << 48));

        // ensure minTickOrOffset is aligned to tickSpacing
        if (minTickOrOffset % tickSpacing != 0) {
            return false;
        }

        // ensure length > 0 and doesn't overflow when multiplied by tickSpacing
        // ensure length can be contained between minUsableTick and maxUsableTick
        if (
            length <= 0 || int256(length) * int256(tickSpacing) > type(int24).max
                || length > maxUsableTick / tickSpacing || -length < minUsableTick / tickSpacing
        ) return false;

        // ensure alpha is in range
@>      if (alpha < MIN_ALPHA || alpha > MAX_ALPHA || alpha == ALPHA_BASE) return false;

        // ensure the ticks are within the valid range
        if (shiftMode == uint8(ShiftMode.STATIC)) {
            // static minTick set in params
            int24 maxTick = minTickOrOffset + length * tickSpacing;
            if (minTickOrOffset < minUsableTick || maxTick > maxUsableTick) return false;
        }

        // if all conditions are met, return true
        return true;
    }

function isValidParams(int24 tickSpacing, uint24 twapSecondsAgo, bytes32 ldfParams) internal pure returns (bool) {
        ...
        // ensure alpha is in range
        if (alpha < MIN_ALPHA || alpha > MAX_ALPHA || alpha == ALPHA_BASE) return false;

@>      // ensure alpha != sqrtRatioTickSpacing which would cause cum0 to always be 0
        uint256 alphaX96 = alpha.mulDiv(Q96, ALPHA_BASE);
        uint160 sqrtRatioTickSpacing = tickSpacing.getSqrtPriceAtTick();
        if (alphaX96 == sqrtRatioTickSpacing) return false;

if (alpha < MIN_ALPHA || alpha > MAX_ALPHA || alpha == ALPHA_BASE) return false;
++      // ensure alpha != sqrtRatioTickSpacing which would cause cum0 to always be 0
++      uint256 alphaX96 = alpha.mulDiv(Q96, ALPHA_BASE);
++      uint160 sqrtRatioTickSpacing = tickSpacing.getSqrtPriceAtTick();
++      if (alphaX96 == sqrtRatioTickSpacing) return false;
LOW severity: Potential erroneous surging when vault token decimals differ from the underlying asset. **Description:** When the vault share prices are computed in `BunniHookLogic::_shouldSurgeFromVaults`, the logic assumes that the vault share token decimals will be equal to the decimals of the underlying asset:

Here, `reserveBalance0/1` is in the decimals of the underlying asset, whereas...

Vulnerable code:
// compute current share prices
uint120 sharePrice0 =
    bunniState.reserve0 == 0 ? 0 : reserveBalance0.divWadUp(bunniState.reserve0).toUint120();
uint120 sharePrice1 =
    bunniState.reserve1 == 0 ? 0 : reserveBalance1.divWadUp(bunniState.reserve1).toUint120();
// compare with share prices at last swap to see if we need to apply the surge fee
// surge fee is applied if the share price has increased by more than 1 / vaultSurgeThreshold
shouldSurge = prevSharePrices.initialized
    && (
        dist(sharePrice0, prevSharePrices.sharePrice0)
            > prevSharePrices.sharePrice0 / hookParams.vaultSurgeThreshold0
            || dist(sharePrice1, prevSharePrices.sharePrice1)
                > prevSharePrices.sharePrice1 / hookParams.vaultSurgeThreshold1
    );