fix: enforce CEI ordering in register_solver, deregister_solver, fill… - #152
Open
ironclad-x wants to merge 2 commits into
Open
fix: enforce CEI ordering in register_solver, deregister_solver, fill…#152ironclad-x wants to merge 2 commits into
ironclad-x wants to merge 2 commits into
Conversation
…_intent - deregister_solver: remove SolverRecord and decrement TotalSolvers before the bond transfer so a re-entrant or double-call sees SolverNotRegistered rather than a live record eligible for a second refund. Closes stellar-vortex-protocol#28 - register_solver: persist SolverRecord and increment TotalSolvers before client.transfer() pulls funds in so storage is always consistent with actual contract holdings. Closes stellar-vortex-protocol#27 - fill_intent: write intent state (Filled), filled_at, fill_amount, solver stats, and protocol volume to storage before any dst_client.transfer() executes. A hostile SEP-41 token that re-enters mid-transfer will see the intent already Filled and be rejected. Closes stellar-vortex-protocol#26 Regression tests added for each fix: - double_deregister_rejected_cleanly - solver_record_consistent_with_token_balances_after_register - fill_intent_state_committed_before_transfer_and_double_fill_rejected
…ollision compute_intent_id previously hashed only (user, src_chain, src_amount, timestamp). Two submit_intent calls from the same user with identical args in the same ledger close produced the same intent_id, and the second write silently overwrote the first IntentRecord. Changes: - Add DataKey::UserNonce(Address) — a per-user submit counter stored in instance storage alongside Admin/FeeRecipient etc. - Increment the nonce on every submit_intent call and include it in the compute_intent_id preimage, making same-ledger collisions impossible in practice. - Add Error::IntentAlreadyExists (variant 22) and a .has() guard in submit_intent that rejects rather than silently overwrites on any (extremely unlikely) hash collision. Regression tests added: - same_ledger_identical_intents_produce_distinct_ids - nonce_increments_per_user_across_submissions - different_users_same_params_same_ledger_produce_distinct_ids Closes stellar-vortex-protocol#25
|
@ironclad-x Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix: submit_intent silently overwrites an existing intent record on
collision
Closes #25
Closes #26
Closes #27
Closes #28
What changed
submit_intent— intent_id collision overwrites existing record(#25)
compute_intent_idhashed only(user, src_chain, src_amount, timestamp).Two
submit_intentcalls from the same user with identical args in thesame
ledger close produced the same
intent_id, and the second writesilently
overwrote the first
IntentRecord— the original intent was lost withno
error.
Fix:
DataKey::UserNonce(Address)— a per-user submit counter thatincrements on every
submit_intentcall and is included in thecompute_intent_idpreimage, making same-ledger collisionsimpossible.
Error::IntentAlreadyExists(variant 22) with a.has()guardin
submit_intentthat rejects rather than silently overwrites on anyhash
collision.
deregister_solver— bond returned before record removed (#28)The
SolverRecordwas removed from storage after the bond transferback to
the solver. A re-entrant or sequenced double-call could find the record
still
present and trigger a second refund.
Fix: Remove the record and decrement
TotalSolversfirst — anyrepeat
call hits
SolverNotRegisteredbefore reaching the transfer.register_solver— bond pulled in before record persisted (#27)client.transfer()pulled the bond in before theSolverRecordwaswritten
to storage. A failure between those two steps would leave the contract
holding
funds with no record of whose bond they are.
Fix: Persist the record (and increment
TotalSolvers) before thetransfer so storage is always consistent with actual holdings.
fill_intent— token transfers before state persisted (#26)dst_client.transfer()to the user and fee recipient ran before theintent
state was set to
Filledand before solver/protocol stats were written.A
hostile SEP-41 token that re-entered
fill_intentorslash_solvermid-transfer would have seen the intent still in
Acceptedstate.Fix: Write all state changes (intent state, solver stats, protocol
volume)
to storage before any token transfer executes.
Tests added
same_ledger_identical_intents_produce_distinct_idsnonce_increments_per_user_across_submissionsdifferent_users_same_params_same_ledger_produce_distinct_idsdouble_deregister_rejected_cleanlySolverNotRegistered; no double-refundsolver_record_consistent_with_token_balances_after_registerrecord.bond_amountalways equals tokens held by contractfill_intent_state_committed_before_transfer_and_double_fill_rejectedFilledbefore transfers; second fill returnsIntentAlreadyFilled