Railnet Queue Nominal Conservation

No matter how many partial redemptions happen, or in what order, the queue never records paying out more than it recorded receiving.

Railnet provides infrastructure for onchain asset management. Its QueryRedeemQueue matches queued share-redemption requests with asset amounts supplied by an authorized caller. Requests can settle in pieces and in different orders.

Why It Matters

A funding record can serve several requests. Each redemption consumes part of its input interval and calculates a rounded asset payout. If two requests reused the same interval, the queue could account for more payouts than that record funded.

For example, a record supplying 100 raw asset units must not contribute 120 units to the redemption ledger after several partial claims. The theorem rules out that accounting failure in the model, even when requests redeem out of order.

How This Was Modeled and Proven

We recreated the relevant Solidity functions in an executable Lean interpreter (Contract.lean), defined the accounting rule (Specs.lean), and used Lean to check its preservation across permitted inputs (Proofs.lean). The model keeps the queue's checks, branch structure, integer-floor calculations and 32-iteration redemption loop. That limit applies to one call, not to the number of calls or the length of a history.

The headline theorem, conservation_for_every_finite_history, states the invariant above for every finite sequence of calls that starts from a fresh initialization. We prove it by showing that each kind of call (demand, fulfill, redeem, resolve, retrieve) preserves a bookkeeping property that implies the invariant. A fresh queue satisfies that property, so every state reached after any number of calls, in any order and with any splitting of redemptions, satisfies it too. The same argument gives conservation_for_every_reachable_state, and nominal_conservation_all_histories is the benchmark's reference statement of the same result. A second theorem, conservation_for_callbacks, repeats the result when token transfers call back into the queue or fail partway.

The reference proof has no unfinished goals or custom axioms. Its five exported declarations use only Lean's standard axioms: propext, Classical.choice and Quot.sound. The combined Contract, Specs, Proofs and Compile build completed successfully with 3,243 jobs; the full benchmark build completed with 9,011 jobs.

FunctionTheoremStatus
executeHistoryconservation_for_every_finite_historyProven
executeconservation_for_every_reachable_stateProven
executeCallbackHistoryconservation_for_callbacksProven
Reference tasknominal_conservation_all_historiesProven
Reachable statesderived_queue_induction_targetsProven
Scope
  • Bookkeeping only. We prove that the queue's ledger of payouts stays within its ledger of funding. Fulfill pulls the funding amount from the caller and redeem sends the summed payouts, which we confirmed by reading the code, not with a proof. Token balances are not proven in Lean, and value is not covered. Relating the ledger to real token movement relies on the ERC20 assumption below.
  • One starting point. Histories begin from a fresh initialization of this pinned implementation. Upgrades, imported storage and other deployments need their own models.
  • Simplified token calls. Ordinary histories assume token calls succeed. A separate theorem covers failures and nested callbacks as finite schedules, not full EVM execution. Setup reads of token decimals are taken as given inputs. Return values, approvals, gas, logs and proxy setup are excluded.
  • Hand translation. The Lean model is written by hand from the Solidity. Nothing machine-checks that it matches the Solidity, the generated Yul or the bytecode. The binary lookup's step limit also adds an error branch that Solidity lacks; we did not prove it unreachable.
Source and deployment evidence

The model comes from the verified code of Railnet's QueryRedeemQueue implementation on Base mainnet (chain 8453) at block 52085313: 0xfebd3ca7e01285e27299c7001307a3d64186aa49 on Basescan. The full source (51 files, main file src/vehicles/multi/QueryRedeemQueue.sol, Solidity 0.8.34) is also on Blockscout. This is a Railnet staging deployment, not Base Sepolia.

The queue proxy 0x387d9d519ad7a0147f27ba460fbc1323d6846f77 belongs to the staging MultiVehicle 0xa3b5b485d0bad0e4e5161f44012e1e220ea1ba04 and resolves to this implementation through its beacon. The source pin file in the benchmark records these addresses and the block. The main source file's SHA-256 is 6fb63f25a4af6a64e287291f7a664f265c2b90efe521990e1ccd444a295ab0de.

Blockscout's verification is partial. Compiling the retrieved source with its settings reproduced the deployed executable bytes; only the metadata hash differed. That matches the bytes, but does not relate the Lean model to them.

Verify it yourself
git clone https://github.com/lfglabs-dev/ethereum-verification-benchmark
cd ethereum-verification-benchmark
git checkout d179132bb12d9e2eea6f1987e22c1ca797323ec5
lake build Benchmark.Cases.RailnetV2.QueryRedeemQueueV2.Proofs
lake build Benchmark.Cases.RailnetV2.QueryRedeemQueueV2.Compile

This pinned revision is a commit on the benchmark's main branch. Compile prints the theorem signatures, axiom dependencies and executable regressions. The full-suite build includes a separately labeled existing Velora compatibility repair; the Railnet target builds are the direct check for this case.

Hypotheses

The proof covers any sequence of calls after a fresh initialization. These rows are what applying it to the real contract depends on.

  • assumption: the Lean model matches the Solidity

    Real-code applicability

    We translated the Solidity into Lean by hand. Lean proves the rule for that translation, but nothing checks the translation against the real code. If it differs in a way that affects the accounting, the result may not apply to the contract.

  • assumption: the token behaves like a standard ERC20

    External token

    The ledger numbers match real token movement only if the token moves exactly the requested amount, with no transfer fees, rebasing or balance changes outside transfers. Under that assumption, and given our reading that fulfill pulls the funding amount and redeem sends the summed payouts, the tokens redeem sends out never exceed the tokens fulfill took in. This link is not proved in Lean.

  • runtime precondition: calls pass the contract's checks

    Source-enforced operation guards

    A call only counts if it passes the contract's own checks: authorization, valid IDs, nonzero amounts and overflow-safe arithmetic. A call that fails changes nothing. These are checks in the Solidity, not trust in any outside system.

Learn More