Repository navigation
feat(contract): on-chain idempotency: optional uniqueness of (payer, condition_ref) - #178
Conversation
|
@Cmitchelle7 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 PR has merge conflicts with |
|
@Cmitchelle7 Thanks for the PR. It can't be merged yet. To unblock it: CI is failing. Here's what broke:
Please fix these errors and run the same steps locally before pushing ( Merge conflicts: this branch conflicts with Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge. |
|
@Cmitchelle7 Heads-up: #268 just changed how contract PRs work in this repo. Your PR changes
This should also make your next merge from |
Summary
Closes #47.
A consumer retrying
create_escrowafter a timeout can fund the same agreement twice. Off-chain guards don't see both transactions; the chain does. This adds an opt-inunique_ref: boollast parameter tocreate_escrowand a(payer, condition_ref) → escrow_idindex that lets a retrying client recover the original escrow id.Design decision (raised in issue #47)
Keep the mapping after settlement. A retry that lands after release/refund must still return
DuplicateConditionRef(recoverable viaget_escrow_id_by_ref), not fund a second escrow. Callers who want to recycle acondition_reffor a new agreement opt out withunique_ref = false.Alternative rejected: delete the mapping on settlement — closes the retry-after-timeout hole only until settlement, then re-opens it.
Changes
contracts/escrow/src/lib.rsDataKey::ConditionRef((Address, String))→u64.Error::DuplicateConditionRef = 7.contracterrorenums can't carry a payload, so a retrying client uses the new getter to recover the id.create_escrowgainsunique_ref: boolas the last parameter. Uniqueness is checked before the funding transfer (duplicate retries cost nothing on-chain); the mapping is written after the escrow record and kept acrossrelease/refund.get_escrow_id_by_ref(payer, condition_ref) -> Result<u64, Error>— returns the stored id orNotFound. Works whether the escrow is funded or settled.EscrowCreatedevent gains aunique_ref: boolfield so indexers can distinguish opted-in escrows.contracts/escrow/tests/idempotency.rs— 6 tests:duplicate_ref_returns_error_and_getter_returns_existing_iddifferent_payer_same_ref_is_allowed(no cross-payer collision)unique_ref_false_skips_uniquenesssettled_escrow_still_blocks_same_ref(+unique_ref=falsebypass)get_escrow_id_by_ref_errors_for_unknownstorage_cost_of_ref_key_estimateARCHITECTURE_ESSENTIALS.md/README.md—create_escrowsignature +get_escrow_id_by_refrow +DuplicateConditionRefnoted.ROADMAP.md— Phase E5 section + changelog entry.SDK — deliberately not in this PR
createEscrowshould gainuniqueRef?: booleanand agetEscrowIdByRefmethod, butsdk/src/generated/contract-client.tsis machine-generated (stellar contract bindings typescript) and requires thestellarCLI, which isn't available in this environment. Per repo convention ("never hand-edit generated bindings"), the SDK changes are deferred to a follow-up PR that:uniqueRef/getEscrowIdByReftosdk/src/index.ts,@docforum/escrow-sdk0.3.0 → 0.4.0 (breaking), andTest plan
contracts/escrow/tests/idempotency.rscreate_escrowgained a param — existing calls updated in test files as part of feat(contract): ADR + implementation: reclaim storage for settled escrows #177's base; no other API change)cargo test+cargo build --target wasm32v1-none --release)storage_cost_of_ref_key_estimateoutput pasted belowStorage cost
cargo test storage_cost_of_ref_key -- --nocaptureprints the estimated per-key cost. Local toolchain verification deferred to CI.