Skip to content

A refund_window of 0 means 'unlimited' but is indistinguishable from a misconfiguration #178

Description

@mallison031

Repo context. accensa-contracts holds the on-chain half of Accensa: ReceiptAnchor
(Merkle batch anchoring so an agent can verify it was charged correctly without trusting
the seller's API) and RefundVault (policy-bounded refunds without the merchant becoming
a custodian). Both are deployed on testnet. soroban-sdk 27.0.4, MIT. Read README.md,
docs/SECURITY_MODEL.md and DEPLOYMENTS.md before starting.

Problem

refund() treats a zero window as "no window at all":

let window: u32 = env.storage().instance().get(&DataKey::RefundWindow).unwrap();
if window > 0 {
    let current_ledger = env.ledger().sequence();
    if current_ledger > paid_at_ledger + window {
        return Err(Error::WindowExpired);
    }
}

So 0 disables the single time-bound this contract exists to enforce. Neither initialize nor
set_refund_window says so, rejects it, or flags it. A merchant who passes 0 because they had
not decided on a window yet gets a vault with no expiry, permanently refundable, and nothing
anywhere reports that.

That is the opposite of a safe default. README.md sells the vault as "an enforced time
window"; a vault configured with 0 enforces nothing while still presenting as a RefundVault.

What to do

Decide which of these the contract means, then make the code say it:

  • If unlimited is a legitimate mode, keep it but make it explicit and visible: document it in
    docs/SECURITY_MODEL.md, emit a distinct event when the window is set to 0, and expose the
    current window through a getter so an auditor can see the mode without reading storage.
  • If unlimited is not legitimate, reject 0 in both initialize and set_refund_window
    with a typed error and require a real window. Add a named constant for a sane minimum.

Either way, the magic-number behaviour must stop being implicit.

Acceptance criteria

  • The meaning of refund_window_ledgers == 0 is documented in docs/SECURITY_MODEL.md.
  • Tests cover the chosen behaviour at initialize and at set_refund_window.
  • If 0 remains legal, a getter exposes the current window and an event fires when it changes.
  • The PR states which reading was chosen and why.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions