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
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.
Description
deductandbatch_deductalready support an optionalrequest_id: Option<Symbol>idempotency key, persisted as a processed-request marker in temporary storage (contracts/vault/src/lib.rs:637–:705, helpersrequire_not_duplicate/mark_request_processedat:1196–:1214). The fund-moving counterpartsdeposit(contracts/vault/src/lib.rs:572) andwithdraw(:852) have no such guard, so a backend that retries a timed-out transaction can double-deposit or double-withdraw.This issue brings
depositandwithdrawto parity withdeduct: an opt-in, single-userequest_idthat gives safe at-least-once retry semantics. Likededuct, whenrequest_idisNoneno deduplication is performed, preserving today's behavior.Requirements and context
Functional
deposit(env, caller, amount, request_id: Option<Symbol>) -> Result<i128, VaultError>withdraw(env, amount, request_id: Option<Symbol>) -> Result<i128, VaultError>Self::require_not_duplicate(&env, rid)before any state mutation/transfer andSelf::mark_request_processed(&env, rid)after the successful state write, exactly asdeductdoes (contracts/vault/src/lib.rs:654,:695).StorageKey::ProcessedRequest(Symbol)namespace and TTL constants (REQUEST_ID_BUMP_THRESHOLD/REQUEST_ID_BUMP_AMOUNT) already defined atcontracts/vault/src/lib.rs:185–:186.VaultError::DuplicateRequestIdwith no funds moved.deposit/withdrawevents to carry therequest_idtopic (emptySymbolsentinel whenNone), matching thedeductconvention documented inEVENT_SCHEMA.md(see the "request_id encoding" note).Non-functional / repo conventions
contracts/vault/src/test*.rs,README.md,docs/interfaces/vault.json, andINVARIANTS.md(itsdepositsignature listing).deposit; the duplicate check is a Check.scripts/check-wasm-size.sh).Acceptance criteria
depositandwithdrawacceptrequest_id: Option<Symbol>and reject duplicates withVaultError::DuplicateRequestIdbefore any transfer.Some(id)is single-use within the TTL window;Noneperforms no dedup (existing behavior preserved).deposit/withdrawevents include therequest_idtopic with the empty-Symbolsentinel forNone.is_request_processedreflects deposit/withdraw markers too.EVENT_SCHEMA.md,docs/interfaces/vault.json,README.md, andINVARIANTS.mdupdated for the new parameter and event shape.scripts/coverage.sh).Suggested execution
1. Fork the repo and create a branch
2. Implement changes
contracts/vault/src/lib.rs— add the parameter todepositandwithdraw, wire inrequire_not_duplicate/mark_request_processed, and extend the two events.3. Write/extend tests in
contracts/vault/src/test_idempotency.rs:depositwithSome(id)succeeds, replay returnsDuplicateRequestId(balance unchanged);withdrawwithSome(id)succeeds, replay returnsDuplicateRequestId;Noneallows repeated deposits/withdrawals (no dedup);request_idtopic / empty sentinel;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.shExample commit message
Guidelines
scripts/coverage.sh), CI-enforced via.github/workflows/coverage.yml.///docs on the updated entrypoints; keepEVENT_SCHEMA.mdanddocs/interfaces/vault.jsonauthoritative.