> ## Documentation Index
> Fetch the complete documentation index at: https://shield.fi/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Exact-limit tick crossing

> Why reaching an initialized tick at the price limit still changes active-liquidity state.

When a swap reaches an initialized tick exactly at its price limit, the contract completes the tick transition without moving beyond the limit. This keeps square-root price, current tick, active liquidity, initialized-tick pointers, and fee accounting consistent.

## Crossing condition

The contract defines a crossing as:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
crossed = previous_step_crossed && new_sqrt_price == target_tick_sqrt_price
```

There is no additional requirement that the target tick price differ from `sqrt_price_limit`.

The target is already clamped to the price limit:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
zero_for_one target = max(next_tick_price, limit)
one_for_zero target = min(next_tick_price, limit)
```

The equality case requires this state transition:

* A limit before the next initialized tick stops inside the range and does not cross.
* A limit exactly at the next initialized tick crosses if the step reaches it.
* A limit beyond the next initialized tick allows the same crossing and continued traversal, subject to remaining input and iteration count.

## State inconsistency without equality crossing

Suppose the crossing predicate instead required:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
target_tick_sqrt_price != limit
```

Consider one position with range `[lower, T)` and an upward `one_for_zero` swap that reaches `T` exactly at its price limit.

The resulting state could be:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
sqrt_price = sqrt(T)
current_tick = T
slot.liquidity = prior active liquidity
next_init_above = T
```

The current tick says the position is inactive because its upper boundary is exclusive. The liquidity field still includes it because `liquidity_net` was not applied. This is ghost active liquidity.

If the LP then removed the full position, the normal range check would see `current_tick == tick_upper` and remove zero from pool liquidity. Once the boundary ticks were unlinked, the phantom amount could remain without a position backing it.

## Half-open position ranges

The active-range rule is:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
tick_lower <= current_tick < tick_upper
```

This convention means:

* A position is active at its lower tick.
* A position is inactive at its upper tick.
* Moving upward across an upper tick removes that range's liquidity.
* Moving downward across a lower tick removes that range's liquidity in the opposite direction.

Tick crossing must update current tick and active liquidity according to the same convention used by mint, increase, decrease, and fee settlement.

## State transition by direction

Let `T` be the crossed initialized tick and `net` be its signed `liquidity_net`.

### Upward, `one_for_zero`

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
new_liquidity = old_liquidity + net
new_current_tick = T
new_next_init_below = T
new_next_init_above = tick.next
```

An upper boundary normally has negative net liquidity, so crossing upward removes that range from active liquidity.

### Downward, `zero_for_one`

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
new_liquidity = old_liquidity - net
new_current_tick = T - 1
new_next_init_below = tick.prev
new_next_init_above = T
```

The `T - 1` convention represents the state immediately below the crossed tick.

## Fee growth at the boundary

The crossing flips both token fee-growth-outside accumulators:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
outside0_new = global0_current - outside0_old
outside1_new = global1_current - outside1_old
```

These are wrapping Q128 values represented by two `u128` limbs. The step's LP fee is added to the applicable global accumulator before the flip.

This ordering assigns the boundary step to the liquidity that was active while the price moved to the tick. The next step begins with the post-cross active set.

The transition combines exact-limit crossing with the Q128 fee-growth layout. Clients must use the two-limb Q128 fields when applying the crossing update.

## Price-limit preservation

The crossing updates tick, liquidity, pointers, and fee-growth state while preserving the caller's price cap.

After exact-limit arrival, the next iteration sees a next-tick target that is clamped back to the same current price. Its price interval is zero, so it cannot consume additional swap input or produce output beyond the limit.

Completing the crossing does not change these boundary-step values:

* End square-root price
* Step input consumed
* Step output
* Step fee
* Unspent input at the limit

It does change the public state from which the next quote and future LP accounting begin.

## Quoter state update

An off-chain simulator should persist all post-cross fields even when the user limit equals the tick price:

* Current tick
* Active liquidity
* Next initialized tick below
* Next initialized tick above
* Both fee-growth-outside values on the crossed tick
* Applicable global Q128 fee growth
* Protocol-fee accumulator

Returning only the current order output can hide a state divergence. A market maker may quote the next order with ghost depth even though the previous output was numerically correct.

## Edge cases

### Insufficient input

If the step stops before the initialized tick, `new_sqrt_price != target_tick_sqrt_price`, so there is no crossing.

### Limit between ticks

The clamped target is the limit, not the initialized tick price. There is no crossing.

### Zero liquidity

The tick walk can advance across a zero-liquidity gap without exchanging tokens. If it arrives at an initialized tick exactly at the limit, crossing can activate or deactivate liquidity according to the tick net.

### Final available iteration

A crossing on the final unrolled iteration updates state, but any still-unspent input is stored for refund. The contract does not add another step beyond the fixed count.

### Invalid current-price limit

The public finalize requires the limit to be strictly on the correct side of current square-root price. A caller cannot submit a limit equal to the slot's starting price to force a zero-distance current-tick crossing.

For order controls, return to [Price limits, slippage, and deadlines](../trading/price-limits-slippage-deadlines).
