Skip to content

fix: recompute reservation amounts from miner rate at reserve time#337

Open
anderdc wants to merge 2 commits into
testfrom
fix/reservation-pin-rate-address
Open

fix: recompute reservation amounts from miner rate at reserve time#337
anderdc wants to merge 2 commits into
testfrom
fix/reservation-pin-rate-address

Conversation

@anderdc
Copy link
Copy Markdown
Collaborator

@anderdc anderdc commented May 18, 2026

Swap 79 — what spawned this

Diagnosing swap 79 surfaced that a swap's reservation pins the amounts
(to_amount / tao_amount / from_amount) but not the miner's rate.
The reservation hash and stored Reservation struct commit the validators to
specific amounts, yet nothing tied those amounts back to the rate they were
derived from — so a quote computed against a stale rate could be locked in.

The issue

Stale-quote bounce (swap 79 and every honest user after). handle_swap_reserve
trusted the user-submitted to_amount / tao_amount straight from the synapse.
It checked that the miner quoted a non-zero rate for the direction but never
recomputed the amounts from that rate. Miners update rates often; the rate ticks
between the user's CLI quote and vote_reserve, so nearly every honest
reservation bounced under the strict-equality approach in the previous version
of this PR.

The fix — slippage band (this PR)

Reserve recompute with one-sided slippage. handle_swap_reserve now:

  1. Recomputes the expected to_amount from the commitment rate read at reserve
    time (recompute_reserve_amounts helper, unchanged from the earlier version).
  2. Checks tao_amount internal consistency as pure arithmetic (no rate):
    rejects if synapse.tao_amount != derive_tao_leg(...).
  3. Applies a one-sided slippage gate via quote_within_slippage:
    • A favorable move (recomputed ≥ quoted) always passes.
    • A downward move passes only if recomputed * 10_000 >= quoted * (10_000 - slippage_bps).
    • Uses pure integer math — deterministic across validators.

User-settable via --slippage <percent> on alw swap now (default 2%,
transmitted as synapse.slippage_bps; old clients that omit the field default
to RESERVE_SLIPPAGE_DEFAULT_BPS = 200). Validators clamp to
RESERVE_SLIPPAGE_MAX_BPS = 100_000 (1000%) as an integer/typo guard.

The CLI shows the slippage honestly as a reserve-acceptance threshold — not
a settlement guarantee (rate pinning at settlement is a separate unshipped
change):

Slippage 2% — reservation is rejected if the miner's rate has moved
more than 2% below your quote.

Rejection message and CLI translator: the rate_moved rule in
validator_rejections.py now matches 'quoted amount is below your slippage band'.

Review notes

  • Consensus-path code. quote_within_slippage is pure integer math —
    every validator evaluates the same inputs and reaches the same decision.
    slippage_bps is user-submitted but clamped on every validator before use,
    so a malicious value cannot cause divergence.
  • The reservation still votes with synapse.to_amount / synapse.tao_amount
    the recomputed value is not substituted. Consensus relies on every validator
    using the user's submitted values in vote_reserve.
  • Rejection fires before vote_reserve — no partial on-chain vote on a rejected
    synapse.
  • Tests: 45 pass (12 new in TestReserveRateRecompute); full suite 480/482
    green (2 pre-existing failures unrelated to this PR — missing embit module).

Follow-up — initiate pinning (Part 2, still deferred)

Pinning the miner's commitment (rate + addresses) as of the reserve block is not
included; it needs a small contract change to add reserved_block to the
Reservation struct so R is a deterministic on-chain read. Designed separately
to avoid improvising on the consensus path.

handle_swap_reserve trusted the user-submitted to_amount/tao_amount
without checking them against the miner's commitment rate. A CLI quote
computed against a momentarily-bad or stale rate would get locked into
the reservation, since the reservation pins amounts but not the rate.

Recompute to_amount/tao_amount from the commitment rate read at reserve
time and reject the request when the user-submitted values don't match,
with a "rate moved — re-quote" reason. The CLI rejection translator
gains a matching rate_moved rule so the user is told to re-quote.

Surfaced while diagnosing swap 79.
@xiao-xiao-mao xiao-xiao-mao Bot added the bug Something isn't working label May 18, 2026
Replace strict equality on to_amount with a one-sided slippage band so minor
rate ticks between the user's CLI quote and vote_reserve no longer bounce honest
reservations. The band is configurable via --slippage <percent> on `alw swap now`
(default 2%) and transmitted in a new SwapReserveSynapse.slippage_bps field.
Validators also enforce an internal-consistency check on tao_amount before the
slippage gate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant