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

# Known Issues

> Current limitations of Atlantic's Cairo 1 lane (Rust VM / cairo1-run) and practical workarounds.

Atlantic's Cairo 1 lane runs programs with `cairo1-run` (`--append_return_values`, PIE output). A few upstream `cairo-vm` limitations currently break or restrict common programs. This page describes them and the available workarounds.

## Dictionaries with extra builtins or untaken dict paths

<Warning>
  **Status:** Broken on Atlantic until
  [starkware-libs/cairo-vm#2389](https://github.com/starkware-libs/cairo-vm/pull/2389) is merged and Atlantic deploys
  that `cairo-vm` version.
</Warning>

### What fails

Trace generation for Cairo 1 programs that:

1. Use a **dictionary** (`Felt252Dict` / SegmentArena) **together with** other builtins such as **Poseidon** or **Bitwise** (for example `hades_permutation` or bitwise ops), or
2. Contain **dict code on a branch that is not taken** for the given inputs (SegmentArena is still an implicit of `main`, but no dict is allocated at runtime).

Typical errors:

```text theme={null}
VirtualMachine(Math(RelocatableSubUsizeNegOffset((Relocatable { segment_index: N, offset: 0 }, 2))))
VirtualMachine(Memory(AddressNotRelocatable))
VirtualMachine(Hint((0, VariableNotInScopeError("dict_manager_exec_scope"))))
```

Programs that only use a dict (no Poseidon/Bitwise) or only Poseidon (no dict) often succeed. The combination — or an unused dict path — is what trips the runner.

### Why it happens

Atlantic's Cairo 1 path always enables `--append_return_values` so return values can be written into the output segment for the PIE. After `main` returns, that exit wrapper:

1. Serializes outputs with CASM `rescope` blocks, which **drop AP-relative variables**, including the live **SegmentArena** pointer.
2. Tries to recover SegmentArena from a hard-coded FP offset (`fp + 2 * builtins.len() + 2`). That cell holds the **initial** arena pointer (`base+3`), not the pointer `main` returned. Dict alloc/destruct copies the 3-cell header forward, so validation against the initial pointer is wrong (often a silent no-op). Extra builtins (Poseidon, Bitwise) or gas can shift that FP cell onto a felt or a segment base, which leads to `RelocatableSubUsizeNegOffset` / `AddressNotRelocatable`.
3. Always emits the `RelocateAllDictionaries` cheatcode when SegmentArena is present. The dict manager exec scope is only created when a dict is **actually allocated**. If dict types appear in Sierra but the taken path never allocates one, the hint crashes with `VariableNotInScopeError("dict_manager_exec_scope")`.

Upstream PR [#2389](https://github.com/starkware-libs/cairo-vm/pull/2389) fixes both issues: it stashes the **final** SegmentArena pointer in a dedicated FP-stable local, and treats relocate as a no-op when no dict was allocated.

### Workaround until PR is merged

Generate the PIE **locally** with a fixed `cairo-vm`, then submit that PIE to Atlantic for proof / verification (skip Atlantic's broken Cairo 1 trace step).

1. Use the Herodotus fork (includes the fix):
   [https://github.com/HerodotusDev/starkware-cairo-vm/tree/main](https://github.com/HerodotusDev/starkware-cairo-vm/tree/main)

2. Build and run `cairo1-run` against your Sierra (gas disabled is recommended for this runner):

   ```bash theme={null}
   git clone https://github.com/HerodotusDev/starkware-cairo-vm.git
   cd starkware-cairo-vm
   cargo build -p cairo1-run --release

   ./target/release/cairo1-run path/to/program.sierra.json \
     --layout all_cairo \
     --append_return_values \
     --cairo_pie_output ./pie.zip \
     --args_file path/to/input.txt
   ```

3. Submit `pie.zip` to Atlantic with `pieFile` and the desired result (`PROOF_GENERATION`, `PROOF_VERIFICATION_ON_L1`, or `PROOF_VERIFICATION_ON_L2`). See [Sending Query — Input: Trace File](/atlantic-api/sending-query#input-trace-file-piezip).

Once [#2389](https://github.com/starkware-libs/cairo-vm/pull/2389) is merged and deployed on Atlantic, submitting `programFile` + `inputFile` for these programs should work again without a local PIE step.

## `poseidon_hash_span` / corelib gas helpers (`get_builtin_costs`)

<Warning>
  **Status:** Does not work with `cairo1-run` / Atlantic Cairo 1 when programs are compiled with gas checks stripped
  (the usual Atlantic / `cairo1-run` setup). This is a long-standing upstream limitation, not specific to Atlantic.
</Warning>

### Official documentation

Documented in the upstream `cairo1-run` README:

* [Libfunc `get_builtin_costs` & function `poseidon_hash_many`](https://github.com/starkware-libs/cairo-vm/blob/main/cairo1-run/README.md#libfunc-get_builtin_costs--function-poseidon_hash_many)

Compiling without gas checks removes gas-related libfuncs that the compiler would emit, but it **cannot** remove gas APIs that appear in Cairo source / corelib. Calls into the `gas` corelib module — `withdraw_gas`, `withdraw_gas_all`, and **`get_builtin_costs`** — fail under that runner.

### Important: not only Poseidon hash-span

`core::poseidon::poseidon_hash_span` is the best-known trigger because its implementation calls `get_builtin_costs`. The same failure mode applies to **any** use of those gas libfuncs / helpers — for example other corelib paths that read the builtin cost table or withdraw gas.

Typical symptom when the cost table was never set up:

```text theme={null}
VirtualMachine(FailedToComputeOperands(...))
```

(or related operand / memory failures during the gas helper).

### What to use instead

Prefer hashing via `HashStateTrait` / `hades_permutation`, or a **gas-free** copy of span hashing (same algorithm as corelib, without `get_builtin_costs`).

Upstream example (also under `cairo_programs/cairo-1-programs/poseidon.cairo` in cairo-vm):

```cairo theme={null}
use core::array::{ArrayTrait, SpanTrait};
use core::hash::HashStateTrait;
use core::poseidon::{hades_permutation, HashState};

// Modified version of poseidon_hash_span that doesn't require builtin gas costs
pub fn poseidon_hash_span(mut span: Span<felt252>) -> felt252 {
    _poseidon_hash_span_inner((0, 0, 0), ref span)
}

fn _poseidon_hash_span_inner(
    state: (felt252, felt252, felt252),
    ref span: Span<felt252>,
) -> felt252 {
    let (s0, s1, s2) = state;
    let x = *match span.pop_front() {
        Option::Some(x) => x,
        Option::None => { return HashState { s0, s1, s2, odd: false }.finalize(); },
    };
    let y = *match span.pop_front() {
        Option::Some(y) => y,
        Option::None => { return HashState { s0: s0 + x, s1, s2, odd: true }.finalize(); },
    };
    let next_state = hades_permutation(s0 + x, s1 + y, s2);
    _poseidon_hash_span_inner(next_state, ref span)
}
```

Do **not** call `core::poseidon::poseidon_hash_span` (or other gas-table helpers) in programs you intend to run on Atlantic's Cairo 1 Rust VM until the runner provisions gas the way Starknet does.

## Large program outputs (\~3700+ felts)

<Warning>
  **Status:** Atlantic rejects Cairo 1 runs whose **public output** is larger than about **3700 felts**. This is a
  prover limit on the serialized output segment.
</Warning>

### What fails

Programs whose `main` returns a large `Array<felt252>` (or otherwise writes a large output segment) fail once the output length crosses roughly that threshold — even if the rest of the run is fine.

### Recommended approach

Do **not** return the full array as public output. Return a **Poseidon hash** of the array instead, and **decommit** the preimage on-chain (or elsewhere) only if consumers need the concrete values.

Avoid:

```cairo theme={null}
pub fn main(mut input: Array<felt252>) -> Array<felt252> {
    let mut output: Array<felt252> = array![];
    // Some operation on that array
    // ...

    output
}
```

Prefer:

```cairo theme={null}
pub fn main(mut input: Array<felt252>) -> Array<felt252> {
    let mut output: Array<felt252> = array![];
    // Some operation on that array
    // ...

    // Use the gas-free poseidon_hash_span from the section above —
    // not core::poseidon::poseidon_hash_span
    array![poseidon_hash_span(output.span())]
}
```

The public output is then a single felt (plus array length encoding), which stays well under the limit. Verifiers that need the original array can check `poseidon_hash_span(preimage) == output` when the preimage is supplied on-chain or off-chain.
