Skip to content

feat: Opening Auction implementation#469

Open
z80dev wants to merge 62 commits intomainfrom
opening-auction-wip
Open

feat: Opening Auction implementation#469
z80dev wants to merge 62 commits intomainfrom
opening-auction-wip

Conversation

@z80dev
Copy link
Collaborator

@z80dev z80dev commented Jan 22, 2026

Summary (What this PR adds)

  • A full Opening Auction mechanism for token launches implemented as a Uniswap v4 hook-based, single-swap batch auction
  • OpeningAuction hook contract with full lifecycle management and bid locking
  • OpeningAuctionInitializer to deploy + initialize the auction pool, then transition to Doppler
  • Extensive test coverage across unit, integration, fuzz/invariant, and attack tests

Docs / Spec


High-level Design

  • The auction is a batch sell of a fixed amount of the asset into a v4 pool.
  • Bids are represented as single‑tick liquidity positions (one tick wide) deposited in numeraire only.
  • The hook performs exactly one swap at settlement, selling the auction asset into the pool.
  • External swaps are disabled; the pool exists only for bidding and withdrawal post‑settlement.

On-chain Components

Core contracts

  • src/initializers/OpeningAuction.sol (OpeningAuction)
    • Enforces auction rules, tracks bids and time‑in‑range, performs settlement swap, stores clearingTick.
    • Transfers balances to initializer during migration (incentives retained for claims).
  • src/OpeningAuctionInitializer.sol (OpeningAuctionInitializer)
    • Orchestrates deployment + initialization of auction pool and subsequent Doppler deployment.
    • Permissionless completion; Airlock‑controlled init and exit.

Supporting libraries

  • src/libraries/QuoterMath.sol: simulates swap to compute estimatedClearingTick.
  • src/libraries/PoolTickBitmap.sol: iterates pool tick bitmap for quoter.
  • src/libraries/TickLibrary.sol: tick alignment for pool init + Doppler transition.

Auction Lifecycle

Hook phase (OpeningAuction)

  • NotStarted → Active → Closed → Settled
  • Closed is transient during settleAuction().

Initializer status (OpeningAuctionInitializer)

  • Uninitialized → AuctionActive → DopplerActive → Exited

Token Custody & Flows

Creation

  • Airlock transfers numTokensToSell to the hook.
  • auctionTokens = numTokensToSell * shareToAuctionBps / 10_000.
  • Remaining tokens (dopplerTokens) held temporarily by the hook.

During Active

  • Bidders deposit numeraire only via one‑tick liquidity positions.
  • Hook holds the asset; pool holds numeraire bids.

Settlement

  • Hook swaps asset into the pool.
  • Pool outputs numeraire to the hook.
  • LPs withdraw filled positions to claim asset.

Migration

  • Hook transfers all non‑reserved balances (asset leftovers + proceeds) to initializer.
  • Incentive tokens remain in hook for claims/recovery.

Configuration & Initialization

Per‑auction config (OpeningAuctionConfig)

  • auctionDuration
  • minAcceptableTickToken0, minAcceptableTickToken1 (min price guard)
  • incentiveShareBps
  • tickSpacing, fee, minLiquidity, shareToAuctionBps

Initializer input (OpeningAuctionInitData)

  • auctionConfig, dopplerData, salt (CREATE2 for hook address permissions)

Bidding Rules (Add Liquidity)

Validation in _beforeAddLiquidity:

  • Auction active and not expired
  • Single‑tick position (tickUpper - tickLower == tickSpacing)
  • Min price enforcement via minAcceptableTick
  • Min liquidity per bid

Accounting in _afterAddLiquidity:

  • Decodes owner from hookData
  • Allocates positionId and records AuctionPosition
  • Inserts tick into internal bitmap
  • Updates tick accumulator for incentives
  • Sets reward debt to prevent retroactive rewards
  • Updates liquidityAtTick
  • Recomputes estimatedClearingTick + range state

Bid Withdrawal (Remove Liquidity)

  • During Active, removal is only allowed if the position is out‑of‑range (not expected to fill).
  • Partial removals are not allowed during Active.
  • In Settled, removals are unrestricted.
  • On removal during Active, time‑in‑range is harvested and reward debt updated.

Estimated Clearing Tick & Locking

  • estimatedClearingTick is computed by simulating settlement swap via QuoterMath.
  • Used only for:
    • Range detection
    • Bid locking
    • Time‑in‑range incentives
  • Range tracking uses an internal bitmap of active ticks (not pool bitmap).

Incentives (Time‑in‑Range Accounting)

  • incentiveTokensTotal = totalAuctionTokens * incentiveShareBps / 10_000
  • Time is tracked in Q128 seconds for precision.
  • Reward debt model prevents back‑earning.
  • At settlement:
    • All ticks are finalized to auctionEndTime
    • cachedTotalWeightedTimeX128 becomes denominator
  • Claimable amount:
    • incentiveTokensTotal * positionEarnedTimeX128 / cachedTotalWeightedTimeX128
  • Claim window: 7 days after settlement.

Settlement

Entry: settleAuction() (permissionless)

  • Requires phase == Active and block.timestamp >= auctionEndTime
  • Finalizes tick time accounting
  • Exact‑input swap of:
    • amountToSell = totalAuctionTokens - incentiveTokensTotal - totalTokensSold
  • Enforces min price tick after swap
  • Stores clearingTick and enters Settled

Migration & Incentive Recovery

Migration

  • migrate(recipient) only by initializer, only in Settled
  • Transfers balances excluding reserved incentive tokens

Edge cases

  • recoverIncentives if no earned time exists
  • sweepUnclaimedIncentives after claim window

Initializer Details

initialize(...) (only Airlock)

  • Validates asset/numeraire, tick spacing compatibility, shareToAuctionBps
  • Deploys hook via CREATE2 (hook address must satisfy permission bits)
  • Initializes auction pool at extreme tick (forces numeraire‑only bids)

completeAuction(asset) (permissionless)

  • Settles if needed
  • Migrates balances to initializer
  • Sends proceeds to governance
  • Computes aligned clearingTick for Doppler
  • Deploys Doppler and initializes its pool
  • Updates status to DopplerActive

exitLiquidity(...) (only Airlock)

  • Migrates Doppler balances and marks Exited

Validations, Constraints, and Invariants

Config

  • auctionDuration > 0
  • incentiveShareBps <= 10_000
  • tickSpacing valid
  • minLiquidity > 0
  • Min acceptable ticks in range and aligned

Bids

  • Must be single‑tick, above min price, above min liquidity, include owner

Withdrawals

  • No partial removal in Active
  • In‑range bids locked

Settlement

  • Must be after auctionEndTime
  • Swap cannot violate min price

Known Limitations / Audit Notes

  • Pool is not a trading venue; external swaps are disabled.
  • Gas/DoS risk if excessive active ticks; mitigated by minLiquidity.
  • Position IDs are not fully enumerable on‑chain; indexers should track events.
  • ERC20‑only (native currency unsupported).
  • Newly added ticks that enter range immediately do not emit TickEnteredRange.
  • fee must be static; dynamic fee flags not supported by QuoterMath.

Test Coverage Map

Unit (test/unit/openingauction/)

  • Constructor/config validation
  • Bid validation & owner decoding
  • Position tracking and bitmap behavior
  • Range tracking + clearing tick updates
  • Settlement and incentives math
  • Incentive recovery & tick alignment

Integration (test/integration/)

  • End‑to‑end auction flows
  • Settlement success/failure paths
  • Doppler transition & tick alignment
  • Incentive claim windows, recovery wrappers
  • Attack & max‑tick‑spam scenarios

Invariant (test/invariant/OpeningAuctionInvariants.t.sol)

  • Phase transitions, min price adherence
  • Incentives caps and claim deadlines
  • Bitmap consistency and lock invariants
  • Token sold/proceeds monotonicity

Review Focus (Audit Checklist)

  • Lifecycle correctness (phase transitions + settlement + migration)
  • Min‑price enforcement across estimated and final clearing ticks
  • Bid locking vs withdrawal rules
  • Incentive accounting, reward debt, and denominator caching
  • Hook address / permission‑bit constraints and CREATE2 salt usage
  • Doppler transition correctness (tick alignment + timing mutation)

z80dev added 30 commits January 22, 2026 12:48
Add opening auction contracts and related infrastructure:
- OpeningAuction.sol and OpeningAuctionInitializer.sol
- Supporting interfaces and libraries (PoolTickBitmap, QuoterMath)
- Integration and unit tests for opening auction
- TickLibrary updates
CRITICAL-1 bug fix: _beforeInitialize() was unconditionally setting
isToken0 = true, overwriting the value set by setIsToken0() called
BEFORE pool.initialize(). This broke ~50% of auctions where asset
address > numeraire address (asset is token1).

Changes:
- Add isToken0Set guard state variable
- Make setIsToken0() one-time only with guards (prevents double-set)
- Remove isToken0 = true overwrite in _beforeInitialize()
- Add IsToken0NotSet validation in _beforeInitialize()
- Add IsToken0AlreadySet and IsToken0NotSet errors to interface

Tests added (9 tests in TokenOrdering.t.sol):
- test_setIsToken0_CanOnlyBeCalledOnce
- test_setIsToken0_RevertsAfterInitialization
- test_beforeInitialize_RevertsIfIsToken0NotSet
- test_isToken0True_PreservedThroughInitialization
- test_isToken0False_PreservedThroughInitialization
- test_fullAuctionFlow_AssetIsToken1
- test_bidValidation_UsesCorrectDirectionForToken1Asset
- test_isToken0Set_InitiallyFalse
- test_setIsToken0_OnlyInitializer
CRITICAL-2 bug fix: Partial removal didn't decrement pos.liquidity,
allowing users to over-claim incentives by repeatedly partial-removing
and re-claiming based on stale liquidity values.

Fix: Require full position removal during Active phase. After settlement,
partial removals are allowed since incentive accounting is finalized.

Changes:
- Add PartialRemovalNotAllowed error to interface
- Add check in _beforeRemoveLiquidity: liquidityToRemove must equal pos.liquidity

Tests added (9 tests in PartialRemoval.t.sol):
- test_fullRemoval_OutOfRangePosition_Succeeds
- test_partialRemoval_Reverts
- test_partialRemoval_SlightlyLess_Reverts
- test_partialRemoval_SlightlyMore_Reverts
- test_inRangePosition_CannotBeRemoved_EvenFullRemoval
- test_afterSettlement_FullRemoval_Succeeds
- test_afterSettlement_PartialRemoval_Succeeds
- test_fullRemoval_MinimumLiquidity_Succeeds
- test_multiplePositionsSameTick_IndividualFullRemoval
- Fix position key collision: use owner instead of sender in position key
  hash to prevent collisions when multiple users go through same router
- Add time cap enforcement: cap accumulator updates at auctionEndTime to
  prevent post-auction time accrual if settlement is delayed
- Fix settlement race condition: block liquidity removals when phase is
  Closed to prevent race with cached denominator calculation

These fixes address HIGH and MEDIUM severity issues identified during
deep analysis of the incentive mechanism.
Add invariant tests for the OpeningAuction incentive mechanism:

Invariants tested:
1. Conservation: claimed incentives never exceed incentiveTokensTotal
2. Monotonic accumulators: tick accumulators never decrease
3. Monotonic earned time: position earned time never decreases
4. Settlement finality: claimable amounts frozen after settlement
5. No double claim: cannot claim same position twice
6. Total bounded: pending + claimed <= incentiveTokensTotal
7. Active ticks validity: all active ticks have non-zero liquidity

Includes Handler contract that performs randomized auction operations
(addBid, removeBid, warpTime, settleAuction, claimIncentives) for
thorough fuzz testing of the incentive mechanism.
Add comprehensive integration tests covering real-world token launch scenarios:
- Small cap launches with low incentive shares (0.3% / 60 bps)
- Many bidders with majority ending up out of range (50 bidders, 70%+ out of range)
- Partial time in range scenarios (positions pushed out mid-auction)
- Mixed bidder sizes (whales vs retail distribution)
- Last-minute bidding/sniping behavior
- Very short time in range precision tests
- Complete incentive claiming flow

These tests address gaps identified in test coverage analysis where
previous tests used unrealistic parameters (10% incentives vs real-world 0.3%).
- Add re-initialization guard to prevent duplicate auctions for same asset
- Validate isToken0 in dopplerData matches derived token ordering
- Forward numeraire proceeds to airlock in completeAuction
- Align clearing tick to Doppler's tick spacing before pool init
- Add exitLiquidity target validation with reverse mapping
- Add ReentrancyGuard to completeAuction
- Update pragma to ^0.8.26

New errors: AssetAlreadyInitialized, IsToken0Mismatch, InvalidExitTarget
New event: ProceedsForwarded
- Use minAcceptableTick as sqrtPriceLimitX96 in settlement swap to prevent
  price manipulation between validation and execution
- Add SenderNotPoolManager error for receive() and unlockCallback
- Update pragma to ^0.8.26
- test_completeAuction_forwardsProceedsToAirlock: verifies proceeds forwarding
- test_initialize_revertsWhenAssetAlreadyInitialized: verifies re-init guard
- test_initialize_revertsOnIsToken0Mismatch: verifies isToken0 validation
- Move OpeningAuction.sol to src/initializers/ for consistency
- Remove dead afterSwap hook (~2100 gas savings per swap)
- Add NatSpec documenting zero-bid settlement behavior
- Add comprehensive test coverage (50 new tests):
  - IncentiveRecovery.t.sol: 12 tests for recovery edge cases
  - OpeningAuctionFuzz.t.sol: 21 fuzz tests for tick/amount edges
  - OpeningAuctionToken1.t.sol: 9 tests for isToken0=false flow
  - OpeningAuctionGas.t.sol: 8 gas benchmarks for settlement scaling
…pler timing

- Fix migrate() to exclude incentiveTokensTotal from transfer, ensuring
  users can still claim their LP incentives after auction completion
- Fix _modifyDopplerStartingTick() to auto-adjust timing when the original
  startingTime has passed, preventing Doppler deployment from reverting
- Add DopplerTransition.t.sol with 6 tests covering both fixes
- Emit BidWithdrawn event in _afterRemoveLiquidity when position is removed
- Remove obsolete afterSwap comment (was explaining removed hook)
- Remove unused PositionRolled event from interface (feature not implemented)
- Add OpeningAuctionToken1Direction.t.sol with 9 tests for inverse direction
  - Full auction flow with token1 as asset (price moves UP from MIN_TICK)
  - Bid placement priority, partial fills, withdrawal, multi-bidder scenarios
- Add OpeningAuctionSettlementFailure.t.sol with 4 tests for edge cases
  - SettlementPriceTooLow/TooHigh reverts for both directions
  - No bids scenario (empty activeTicks)
Use bitmap data structure for efficient tick tracking during auction settlement.
Implements position calculation, tick flipping, and directional tick traversal
using the same pattern as Uniswap V3 TickBitmap.

- Add tickBitmap mapping for O(1) tick lookups
- Add _position, _flipTick, _isTickActive helper functions
- Add _nextInitializedTickWithinOneWord and _nextInitializedTick for traversal
- Add _insertTick and _removeTick with min/max tracking
- Add comprehensive bitmap unit tests (54 tests)
…ifecycle

Add 10 new events for better off-chain indexing and monitoring:

IOpeningAuction.sol:
- AuctionStarted: full config details on auction initialization
- PhaseChanged: track NotStarted→Active→Closed→Settled transitions
- TickEnteredRange/TickExitedRange: tick range boundary changes
- LiquidityAddedToTick/LiquidityRemovedFromTick: per-tick liquidity tracking
- TimeHarvested: incentive time harvesting per position
- PositionRolled: position tick changes

OpeningAuctionInitializer.sol:
- AuctionInitialized: deployment config and addresses
- DopplerTransitionStarted: auction→doppler phase transition
New test files covering previously untested scenarios:

TickSpacingEdgeCases.t.sol (15 tests):
- Tick alignment at min/max boundaries
- Common tick spacings (1, 10, 60, 200)
- Fuzz tests for alignment invariants
- Token0/Token1 rounding direction validation

OpeningAuctionStress.t.sol (5 tests):
- 50 unique bidders with settlement
- Concentrated bids at single tick
- Gas scaling with 75 active ticks
- Many incentive claims scenario
- Rapid bid add/remove churn

OpeningAuctionAttacks.t.sol (8 tests):
- Flash loan manipulation attempts
- Settlement sandwich attack vectors
- Price manipulation below minAcceptableTick
- TOCTOU protection validation
- Last-block manipulation scenarios

OpeningAuctionConcurrent.t.sol (6 tests):
- Multiple simultaneous auctions
- Cross-auction interference checks
- Shared bidder across auctions
…nditions

- Add clearingTick == minAcceptableTick exact boundary test
- Add estimatedClearingTick vs actual clearingTick accuracy verification
- Add position increase creates new position (not modify existing)
- Add settlement failure when clearing tick below minimum acceptable
- Add same-block settlement timing test
- Add auction end exact timestamp boundary test
- Add cross-contract state consistency tests (Initializer <-> Hook)
- Add phase transition verification tests
- Add liquidity tracking consistency tests

17 new integration tests covering previously untested edge cases
- Add alignTick tests for isToken0=true direction (rounds down/negative)
- Add alignTick tests for isToken0=false direction (rounds up/positive)
- Add tests for various tick spacings (60, 200, 1)
- Add already-aligned tick verification
- Add negative/positive tick boundary tests
- Add MIN_TICK/MAX_TICK boundary behavior tests
- Document that alignTick can exceed tick bounds (caller must clamp)
- Add fuzz tests for alignment properties

18 new unit tests verifying tick alignment math for auction->Doppler transition
- Add test for exitLiquidity revert when status is not DopplerActive
- Add test for exitLiquidity revert when target is invalid address
- Add state transition verification tests
- Add status enum validation tests

4 new tests for exitLiquidity error handling and state transitions
Support both abi.encodePacked (20 bytes) and abi.encode (32+ bytes)
address formats in hookData, enabling more gas-efficient encoding
while maintaining backward compatibility.
Verify that abi.encodePacked addresses work correctly for
adding and removing liquidity through the hook.
Add tests for:
- Estimated clearing tick accuracy after out-of-range removals
- Settlement with mixed in-range and out-of-range positions
- Incentive claims for positions that go out of range
- Zero incentives for positions that never enter range
@z80dev z80dev marked this pull request as ready for review January 22, 2026 20:33
@z80dev
Copy link
Collaborator Author

z80dev commented Jan 27, 2026

ack/fix pushed - addressed all review comments:

  • Removed native token handling from OpeningAuctionPositionManager (ERC20-only)
  • Changed sweepAuctionIncentives and recoverAuctionIncentives to onlyAirlockOwner
  • Defined modifier locally to avoid modifying shared ImmutableAirlock

- Remove native token handling from OpeningAuctionPositionManager
  (docs state ERC20-only support)
- Change sweepAuctionIncentives and recoverAuctionIncentives from
  onlyAirlock to onlyAirlockOwner (Airlock can't call these functions)
- Define onlyAirlockOwner modifier locally to avoid modifying shared
  ImmutableAirlock base contract
@z80dev
Copy link
Collaborator Author

z80dev commented Jan 27, 2026

Addressed remaining review feedback in 7ca86e1: token1 min-acceptable tick check now uses tickUpper (ceiling) and token1 tests/docs updated to cover the boundary case; auction config fields (incl. totalAuctionTokens) are now immutable. Thanks for the catches!

Replace _floorToSpacing with alignTick in _updateClearingTickAndTimeStates
to ensure consistent "in range" behavior between isToken0=true and
isToken0=false auctions.

Previously, _floorToSpacing always rounded toward negative infinity,
causing isToken0=true auctions to lock more positions (conservative)
while isToken0=false auctions locked fewer positions (not conservative).

The fix uses alignTick which rounds down for isToken0=true and rounds up
for isToken0=false, making both auction types equally conservative.

Adds test demonstrating the asymmetry and verifying the fix.
@z80dev
Copy link
Collaborator Author

z80dev commented Jan 28, 2026

Addressed in 2c67f10: replaced _floorToSpacing with alignTick(isToken0, ...) in _updateClearingTickAndTimeStates() to ensure symmetric clearing tick rounding between isToken0=true and isToken0=false auctions. Added test case demonstrating the asymmetry.

emit TickExitedRange(nextTick, liquidityAtTick[nextTick]);
}

iterTick = nextCompressed + 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should instead be iterTick = nextCompressed. Since we're using lte: false, we increment the provided tick in _nextInitializedTickWithinOneWord to start from the word of the next tick. So seems like we skip a tick as a result of this

Note that we do the same thing in _finalizeAllTickTimes and _sumActiveTickTimes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized the reason this works is because we pass iterTick - 1 to _nextInitializedTick. Would be slightly more efficient to initialize iterTick as startCompressed - 1 and then set do iterTick = nextCompressed at the end of each loop, but this is logically the same as what we currently have

Btw, I noticed that _nextInitializedTickWithinOneWord/_nextInitializedTick differ between the actual implementations and those in BitmapUnit.t.sol. Would recommend using a mock contract to point a public function to these internal functions for tests instead of the current pattern so that it doesn't have to be manually updated

cc @z80dev

Comment on lines +1246 to +1249
bool enteringRange = isToken0 ? newClearingTick < oldClearingTick : newClearingTick > oldClearingTick;
if (enteringRange) {
// More ticks are now filled - walk ticks that entered range
_walkTicksEnteringRange(oldClearingTick, newClearingTick, tickSpacing);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the entering range case altogether. Since we can only add in range liquidity, we can only really decrease the range. The only exception is that when the first position is added we go from oldClearingTick = MAX_TICK to the new clearing tick. But here we're at most only actually marking one tick as in range, which is done later in _afterAddLiquidity regardless

// For isToken0=false (!zeroForOne swap, price moves up from MIN_TICK):
// Position is utilized if clearing tick is at or above tickLower
// (price moved up into or through the range)
return estimatedClearingTick >= tick;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we always rounded the estimatedClearingTick down, but now we round up if !isToken0. Before, the equality case made sense because of that, but now it will consider a tick to be in range if the actual clearing tick is any greater than tick - tickSpacing

Should instead be:

return estimatedClearingTick > tick

Comment on lines +1421 to +1422
int24 finalTick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
if (_tickViolatesPriceLimit(finalTick)) revert SettlementPriceTooLow();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we start with a minimum tick value, minAcceptableTickToken0/minAcceptableTickToken1, then convert that tick into a sqrt price, and then here convert the sqrt price back into a tick before this important validation, we are assuming that tick == getTickAtSqrtPrice(getSqrtPriceAtTick(tick))

I'm pretty confident this is correct, but ideally we should write a fuzz test to prove this, especially since triggering this revert would be a big problem

@kadenzipfel
Copy link
Collaborator

One minor concern I have is that if we reward incentives to all bids which are at least partially in range, it would be possible to continually place bids that are only slightly in range, e.g. via a script, receiving the same incentives as other bids which are fully in range, while only ever having to buy a small amount of tokens if any

I'm not sure if it's better to only incentivize bids that are fully in range though. It might honestly be partly a good thing as if bots competed to do this it would push the clearing price up. I'm not sure, just something I've been thinking about

@z80dev
Copy link
Collaborator Author

z80dev commented Jan 30, 2026

I tried pretty hard to reproduce an actual skip here and couldn’t, so I added regression tests that exercise the real production walker.

What I did / why I think it’s fine:

  • I added a small harness that inherits the real OpeningAuction and directly calls _walkTicksRange(...) (no copied logic), then uses vm.recordLogs() to capture the emitted TickEnteredRange(int24 indexed tick, …) events.
  • The tests assert the emitted ticks are:
    • strictly increasing (no duplicates / no getting stuck)
    • and the emitted set matches exactly the inserted active ticks within bounds (no skips)

Coverage:

  • deterministic consecutive ticks (0/60/120/180 @ spacing 60)
  • word boundary case (compressed 255 + 256)
  • fuzz/property-ish test: random subset of ticks in [-32,32] and assert visited set == expected set
  • explicit tests for clamping to min/max active bounds and unaligned start/end (ceil/floor behavior)

Also tested the suggested change:
I modeled the “fix” of setting iterTick = nextCompressed (instead of nextCompressed + 1) using the real internal helpers, and that version fails to make progress (loops / keeps returning the first initialized tick). That’s why the +1 advance exists.

Commits on this branch:

  • 9961090 (initial regression tests)
  • 15267a0 (extra clamping/alignment coverage)

If you have a specific edge case in mind that you think I’m missing, I’m happy to add it.

Comment on lines +492 to +494
// Try to resolve asset from Doppler hook mapping first
address asset = dopplerHookToAsset[target];
// If not found, try the Opening Auction hook mapping (Airlock passes this address)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will never work as target is auctionHook, but this mapping returns the dopplerHook

- QuoterMath: add protocol fee support by reading protocolFee/lpFee from
  Slot0 and using ProtocolFeeLibrary.calculateSwapFee()
- OpeningAuctionInitializer: pre-validate Doppler constructor params to
  prevent locked funds if deployment fails
- OpeningAuction: remove unused param names, update outdated comment
- BitmapUnit.t.sol: use inheritance-based harness to test production code
- Add TickMathRoundTrip fuzz tests for tick->sqrtPrice->tick assumption

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
) = _decodeDopplerInitData(initData.dopplerData);

// Check numPDSlugs: must be > 0 and <= 15
if (dopplerNumPDSlugs == 0 || dopplerNumPDSlugs > 15) revert InvalidNumPDSlugs();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we use the MAX_PRICE_DISCOVERY_SLUGS constant directly here instead of 15 in case it gets updated in the future

if (dopplerGamma <= 0) revert InvalidGamma();

// Check tick spacing: must be <= 30
if (dopplerTickSpacing_ > 30) revert InvalidDopplerTickSpacing();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use MAX_TICK_SPACING directly instead of 30 here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants