Skip to content

escrow's REFUND_GRACE_SECONDS is a fixed global constant instead of a tunable per-escrow parameter #19

Description

@abayomicornelius

Problem

escrow/src/lib.rs has exactly three state-changing operations: create_escrow, release_escrow, and refund_escrow (lines 86-194). There is no way for depositor and beneficiary to mutually agree to unwind an escrow early — e.g. a trade falls through by mutual agreement before unlock_time, with no arbiter configured (arbiter: None). In that scenario, funds are locked until either unlock_time (beneficiary can then release_escrow to themself, but they may not want the funds — they want to give them back!) or unlock_time + REFUND_GRACE_SECONDS (depositor can self-refund), whichever the parties don't actually want.

Why it matters

Without an arbiter, two cooperating parties have no way to short-circuit an escrow they both agree should be canceled — they're stuck waiting out unlock_time + REFUND_GRACE_SECONDS in the worst case (over a week past the original unlock point) even though there's no actual dispute. This is a real usability gap for the common "we agreed to cancel the deal" case, distinct from the adversarial cases the existing release/refund logic is designed around.

Proposed fix

Add a mutual_cancel_escrow(env: Env, escrow_id: u64, depositor: Address, beneficiary: Address) -> Result<(), EscrowError> requiring both depositor.require_auth() and beneficiary.require_auth() in the same call (Soroban supports multiple auth entries in one invocation), refunding the full amount to depositor (or, alternatively, requiring the parties to specify a split — simpler to always refund depositor in full and let the beneficiary's consent alone be the safety check, since if they wanted differently they wouldn't consent).

Edge cases

  • Must still respect assert_locked — an already-released/refunded escrow can't be mutually cancelled.
  • If an arbiter is set, should mutual cancellation still be available (bypassing the arbiter entirely since both principals agree), or should it be disallowed in favor of routing through the arbiter? Given the arbiter's whole purpose is dispute resolution and there's no dispute in the mutual-consent case, allowing it seems reasonable, but worth explicit maintainer sign-off since it changes the arbiter's implied authority.
  • Should emit its own event (escrow_cancelled) distinct from emit_escrow_refunded so indexers can distinguish "cooperative cancel" from "unilateral depositor refund after grace period," since they have different risk/trust implications.

Testing strategy

Add test_mutual_cancel_requires_both_signatures (fails if either party's auth is missing), test_mutual_cancel_refunds_depositor, test_mutual_cancel_rejected_if_already_released, and test_mutual_cancel_rejected_if_already_refunded.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26contractsSmart contract logicenhancementNew feature or requestvery hardVery difficult / senior-level bounty issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions