nautechsystems/nautilus_trader · error

Pool fee should be initialized

Error message

Pool fee should be initialized

What it means

simulate_swap_through_ticks reads self.pool.fee to get the fee tier used in swap math; if the fee was never initialized on the pool, expect panics. The fee tier is essential for computing fees accrued within each swap step, so simulation cannot proceed without it.

Source

Thrown at crates/model/src/defi/pool_analysis/profiler.rs:494

    /// # Errors
    ///
    /// Returns error if:
    /// - Pool fee is not configured
    /// - Fee growth arithmetic overflows when scaling by liquidity
    /// - Swap step calculations fail
    ///
    /// # Panics
    ///
    /// Panics if the pool fee has not been initialized.
    pub fn simulate_swap_through_ticks(
        &self,
        amount_specified: I256,
        zero_for_one: bool,
        sqrt_price_limit_x96: U160,
        traverse_empty_ranges: bool,
    ) -> anyhow::Result<SwapQuote> {
        let exact_input = amount_specified.is_positive();
        let fee_tier = self.pool.fee.expect("Pool fee should be initialized");

        let mut current_sqrt_price = self.state.price_sqrt_ratio_x96;
        let mut current_tick = self.state.current_tick;
        let mut current_active_liquidity = self.tick_map.liquidity;
        let mut amount_specified_remaining = amount_specified;
        let mut amount_calculated = I256::ZERO;
        let mut protocol_fee = U256::ZERO;
        let mut lp_fee = U256::ZERO;
        let mut crossed_ticks = Vec::new();

        // Swapping cache variables
        let fee_protocol = self.state.uniswap_v3_fee_protocol(zero_for_one);
        let fee_protocol_basis_points = self.state.fee_protocol_basis_points(zero_for_one);

        // Track current fee growth during swap
        let mut current_fee_growth_global = if zero_for_one {
            self.state.fee_growth_global_0
        } else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Initialize pool.fee from the factory/pool fee() call or the mint/create event before running swap simulation.
  2. Call check_if_initialized (or an equivalent guard) and return an error instead of proceeding when fee is None.
  3. Validate pool completeness (fee, tick_spacing) at profiler construction so uninitialized pools fail early with a clear error.

Example fix

// before
let fee_tier = self.pool.fee.expect("Pool fee should be initialized");
// after
let fee_tier = self.pool.fee.ok_or_else(|| anyhow::anyhow!(
    "pool {} fee not initialized; cannot simulate swap", self.pool.address
))?;
Defensive patterns

Strategy: validation

Validate before calling

if self.pool.fee.is_none() { return Err(anyhow::anyhow!("pool fee not initialized")); }

Type guard

fn fee_tier(pool: &SharedPool) -> Option<u32> { pool.fee }

Try / catch

match self.pool.fee { Some(f) => simulate_with(f), None => Err(anyhow!("fee missing")), }

Prevention

When it happens

Trigger: Calling simulate_swap_through_ticks (directly or via process_swap, execute_swap, quote_swap) on a profiler whose pool.fee is None — i.e. the pool's fee rate was not captured during topology initialization.

Common situations: Quoting swaps against a pool seeded from partial data; pools discovered via RPC where the fee() call failed silently; test fixtures building SharedPool without fee.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/ab9fdb65670a7b94. Report an issue: GitHub.