Skip to content

fix(order-vault): gate record_transfer_in behind CONTROLLER auth - #703

Merged
IbrahimIjai merged 1 commit into
SO4-Markets:mainfrom
dev-debbie-umoh:fix/541-order-vault-unauthenticated-record-transfer-in
Aug 31, 2026
Merged

fix(order-vault): gate record_transfer_in behind CONTROLLER auth#703
IbrahimIjai merged 1 commit into
SO4-Markets:mainfrom
dev-debbie-umoh:fix/541-order-vault-unauthenticated-record-transfer-in

Conversation

@dev-debbie-umoh

Copy link
Copy Markdown
Contributor

Summary

Issue #386 ("order_vault: unauthenticated record_transfer_in lets deposits be stolen or stranded") was closed, but record_transfer_in in contracts/order_vault/src/lib.rs was never actually changed — it remained fully public with no require_auth()/CONTROLLER check, unlike transfer_out in the same file.

Anyone could call order_vault.record_transfer_in(token) directly right after observing a victim's deposit land but before the victim's own create_order call executes. This resyncs the recorded balance to the current on-chain balance, so the victim's subsequent create_order sees delta == 0, reverts with ZeroCollateral, and the victim's tokens are stuck in the vault (transfer_out is CONTROLLER-gated and only ever invoked against a matching OrderProps that was never created).

Fix

  • record_transfer_in now takes a caller: Address, calls caller.require_auth(), and is gated to CONTROLLER — mirroring transfer_out in the same file.
  • Updated the two order_handler call sites (create_order, create_orders) to pass the handler's own address as caller, matching how they already call transfer_out.
  • Added a regression test asserting a non-CONTROLLER caller cannot invoke record_transfer_in directly.

Closes #541

Test plan

  • cargo test -p order-vault — 8 passed, 0 failed (includes new record_transfer_in_by_non_controller_panics)
  • cargo test -p order-handler --lib — 63 passed, 0 failed (3 unrelated pre-existing failures on unmodified main too: custom_heartbeat_timeout_is_respected, execute_order_records_keeper_heartbeat, keeper_goes_stale_after_timeout_and_role_is_revocable — oracle price-lookup issue unrelated to this change, verified independently)

 SO4-Markets#541)

record_transfer_in resyncs the vault's recorded per-token balance and
was fully public — callable by any address at any time, unlike every
other state-mutating function in this contract (transfer_out is
CONTROLLER-gated). This let an attacker front-run a victim's deposit:
call record_transfer_in right after the victim's collateral transfer
lands but before their create_order executes, resyncing the recorded
balance so create_order sees delta == 0 and reverts with
ZeroCollateral — stranding the victim's tokens in the vault with no
path to reclaim them.

Gate it to CONTROLLER, mirroring transfer_out in the same file, and
update the two order_handler call sites (create_order, create_orders)
to pass the handler's own address as caller, matching how they already
call transfer_out.
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@dev-debbie-umoh 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! 🚀

Learn more about application limits

@IbrahimIjai
IbrahimIjai merged commit b6a1fa3 into SO4-Markets:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

order_vault: closed issue #386's unauthenticated record_transfer_in is still exactly unfixed

2 participants