Skip to content

Add optional request_id idempotency to vault deposit and withdraw for retry-safe operations #402

@greatest0fallt1me

Description

@greatest0fallt1me

Description

deduct and batch_deduct already support an optional request_id: Option<Symbol> idempotency key, persisted as a processed-request marker in temporary storage (contracts/vault/src/lib.rs:637:705, helpers require_not_duplicate / mark_request_processed at :1196:1214). The fund-moving counterparts deposit (contracts/vault/src/lib.rs:572) and withdraw (:852) have no such guard, so a backend that retries a timed-out transaction can double-deposit or double-withdraw.

This issue brings deposit and withdraw to parity with deduct: an opt-in, single-use request_id that gives safe at-least-once retry semantics. Like deduct, when request_id is None no deduplication is performed, preserving today's behavior.

Requirements and context

Functional

  • Change signatures to accept an optional id:
    • deposit(env, caller, amount, request_id: Option<Symbol>) -> Result<i128, VaultError>
    • withdraw(env, amount, request_id: Option<Symbol>) -> Result<i128, VaultError>
  • Reuse the existing idempotency machinery: call Self::require_not_duplicate(&env, rid) before any state mutation/transfer and Self::mark_request_processed(&env, rid) after the successful state write, exactly as deduct does (contracts/vault/src/lib.rs:654, :695).
  • Markers must share the same StorageKey::ProcessedRequest(Symbol) namespace and TTL constants (REQUEST_ID_BUMP_THRESHOLD / REQUEST_ID_BUMP_AMOUNT) already defined at contracts/vault/src/lib.rs:185:186.
  • On duplicate, return VaultError::DuplicateRequestId with no funds moved.
  • Extend the emitted deposit / withdraw events to carry the request_id topic (empty Symbol sentinel when None), matching the deduct convention documented in EVENT_SCHEMA.md (see the "request_id encoding" note).

Non-functional / repo conventions

  • This is a signature change: update all call sites in contracts/vault/src/test*.rs, README.md, docs/interfaces/vault.json, and INVARIANTS.md (its deposit signature listing).
  • Keep the Checks-Effects-Interactions structure already documented on deposit; the duplicate check is a Check.
  • Stay within the WASM size budget (scripts/check-wasm-size.sh).

Acceptance criteria

  • deposit and withdraw accept request_id: Option<Symbol> and reject duplicates with VaultError::DuplicateRequestId before any transfer.
  • Some(id) is single-use within the TTL window; None performs no dedup (existing behavior preserved).
  • deposit / withdraw events include the request_id topic with the empty-Symbol sentinel for None.
  • is_request_processed reflects deposit/withdraw markers too.
  • EVENT_SCHEMA.md, docs/interfaces/vault.json, README.md, and INVARIANTS.md updated for the new parameter and event shape.
  • Line coverage stays >= 95% (scripts/coverage.sh).

Suggested execution

1. Fork the repo and create a branch

git checkout -b feature/vault-deposit-withdraw-idempotency

2. Implement changes

  • contracts/vault/src/lib.rs — add the parameter to deposit and withdraw, wire in require_not_duplicate / mark_request_processed, and extend the two events.
  • Update docs listed above.

3. Write/extend tests in contracts/vault/src/test_idempotency.rs:

  • first deposit with Some(id) succeeds, replay returns DuplicateRequestId (balance unchanged);
  • first withdraw with Some(id) succeeds, replay returns DuplicateRequestId;
  • None allows repeated deposits/withdrawals (no dedup);
  • event carries the expected request_id topic / empty sentinel;
  • marker TTL/expiry behavior mirrors the existing deduct idempotency tests.

4. Test and commit

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --workspace
./scripts/check-wasm-size.sh
./scripts/coverage.sh

Example commit message

feat(vault): add optional request_id idempotency to deposit and withdraw

Guidelines

  • Coverage must stay >= 95% (scripts/coverage.sh), CI-enforced via .github/workflows/coverage.yml.
  • NatSpec-style /// docs on the updated entrypoints; keep EVENT_SCHEMA.md and docs/interfaces/vault.json authoritative.
  • Timeframe: 96 hours.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions