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.
Problem
refund()treats a zero window as "no window at all":So
0disables the single time-bound this contract exists to enforce. Neitherinitializenorset_refund_windowsays so, rejects it, or flags it. A merchant who passes0because they hadnot 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.mdsells the vault as "an enforced timewindow"; a vault configured with
0enforces nothing while still presenting as a RefundVault.What to do
Decide which of these the contract means, then make the code say it:
docs/SECURITY_MODEL.md, emit a distinct event when the window is set to0, and expose thecurrent window through a getter so an auditor can see the mode without reading storage.
0in bothinitializeandset_refund_windowwith 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
refund_window_ledgers == 0is documented indocs/SECURITY_MODEL.md.initializeand atset_refund_window.0remains legal, a getter exposes the current window and an event fires when it changes.