Skip to content

Feature/129 refund vault refactor - #254

Open
tecmalik wants to merge 4 commits into
accensa:mainfrom
tecmalik:feature/129-refund-vault-refactor
Open

Feature/129 refund vault refactor#254
tecmalik wants to merge 4 commits into
accensa:mainfrom
tecmalik:feature/129-refund-vault-refactor

Conversation

@tecmalik

@tecmalik tecmalik commented Aug 27, 2026

Copy link
Copy Markdown

Closes #129


Title
feat(refund-vault): split into factory + stateless policy modules (#129)
Summary
Refactors RefundVault into a multi-contract architecture so refund-policy rules can evolve without redeploying (or re-auditing) the vault itself.
What changed

  • RefundVaultFactory (new contracts/refund-vault-factory): deploys individual RefundVault instances via deploy_v2 + __constructor, stores the vault wasm hash on-chain, keeps a registry of deployed vaults (get_vault, get_vault_count), and supports deploy (default policy) and deploy_with_policy (explicit bind). Emits a VaultCreatedEvent per deployment.
  • RefundWindowPolicy (new contracts/refund-window-policy): a stateless policy implementing check_refund (the refund-window rule; window 0 = unbounded). Deployed as the default policy bound by the factory.
  • RefundVault refactor:
  • Constructor-only lifecycle: initialize removed in favor of __constructor(merchant, token, refund_window_ledgers, refund_policy), mirroring ReceiptShard. A deployed vault is always initialized.
  • refund() no longer evaluates the window inline — it calls the bound policy contract via the RefundPolicy trait (same cross-contract pattern as ReceiptAnchor's ShardInterface), propagating deliberate policy rejections (WindowExpired) and mapping host-level call failures to new Error::PolicyCallFailed (302).
  • Adds get_refund_policy() and admin-gated set_refund_policy().
    Tests & verification
  • Full workspace cargo test green (0 failures), including new factory (8) and policy (6) test suites, and the existing vault unit/integration/multisig suites.
  • Clippy clean on the vault, factory, and policy crates; cargo fmt applied.
    Deployment / tooling
  • deploy.sh updated: deploy policy + factory, install vault wasm, initialize factory, then factory.deploy for the merchant vault.
  • .wasm-budget.json and CI updated for the new/changed contract wasms.
    ⚠️ Breaking / migration notes
  • RefundVault.initialize is gone; and a vault's refund window is now enforced by its bound policy contract, not inline. Any existing direct-deployed vaults / tooling that called initialize must switch to factory-per-deployed vaults.

Provide a brief summary of the changes in this pull request and the problem being solved.

Contract Change Safety Checklist

Please verify that your changes adhere to contract stability requirements:

  • Event Shapes: Does this PR modify event topic tuples or data shapes? (Breaking change per docs/EVENTS.md)
  • Storage Layout: Does this PR change storage keys or layout? (Assessed for archival & migration risks)
  • Error Variants: Does this PR add or renumber contract error codes? (Client-visible breaking change)
  • Changelog: Has a corresponding entry been added to CHANGELOG.md?
  • Deployments: Has any impact on deployed contracts or DEPLOYMENTS.md been documented?
  • Verification: Has this change been tested locally (cargo test) and/or exercised on Soroban testnet?

Related Issues

Closes #

…censa#129)

Introduce the multi-contract refund architecture:

- contracts/refund-window-policy: a stateless policy contract whose
  check_refund() evaluates the refund-window rule. Other policy kinds are new
  contracts implementing the same fixed signature, so the vault wasm never
  changes when a policy rule changes.
- RefundVault: now constructor-only (__constructor(merchant, token, window,
  policy)), mirroring ReceiptShard. Removed initialize. refund() routes its
  window check to the bound policy contract via the RefundPolicyClient trait
  (same pattern as ReceiptAnchor's ShardInterface), translating deliberate
  policy errors (WindowExpired) while mapping host-level failures to the new
  Error::PolicyCallFailed. Adds get_refund_policy() and set_refund_policy().
- contracts/refund-vault-factory: deploys RefundVault instances via
  deploy_v2 + __constructor, stores the vault wasm hash, keeps an on-chain
  registry of deployed vaults, and supports deploy() (default policy) and
  deploy_with_policy() (explicit policy bind).
- Add Error::PolicyCallFailed (302) to accensa_common.

Unit/integration/multisig test helpers now register vaults with constructor
args; "uninitialized calls fail" vault tests were removed (a deployed vault is
always initialised) and replaced where valuable by policy-call and
constructor-init coverage.

WIP: tests not fully green; superuser deployment/dynamic factory deploy is an
open work item.
…#129)

Resolve the test failures that followed the factory + stateless-policy
refactor:

- Fix all vault unit tests registering the constructor with plain integer
  literals (100 / 17_280), which the `env.register` constructor-deserialization
  inferred as `i32` and rejected with ConversionError. Type them `u32` to match
  the `__constructor(merchant, token, refund_window_ledgers, policy)` signature.
- Rework the three directly-initialised vault tests that no longer apply:
  - self-transfer tests now assert no token movement instead of a fragile
    event count that is unmeasurable after a failed `try_*` sub-call
  - `test_transfer_admin_requires_auth` uses `#[should_panic]` like its
    siblings (the constructor-based setup surfaces the auth abort correctly)
  - removed `test_set_yield_strategy_uninitialized_fails` (a constructor-only
    vault is always initialised; coverage now lives in the factory tests)
- Fix factory tests: `get_refund_policy` returns `Option<Address>`, so wrap
  the expected policy in `Some(...)`.

Verification: full workspace `cargo test` is green (19 ok, 0 failed); clippy
is clean (-D warnings) on the vault, factory and policy crates; `cargo fmt`
applied. Update .wasm-budget.json, ci.yml (cross-contract wasm build + size
budget for the three refund contracts), CHANGELOG.md and deploy.sh (policy +
factory deploy, then factory.deploy for the merchant vault).

Note: `cargo clippy --all-targets -- -D warnings` still fails on two
pre-existing lints outside this change (receipt-anchor u32 cast, and the same
nonminimal_bool in fuzz_test that this branch already pinned) — both fail on
the clean baseline under the local rust 1.96.0 toolchain.
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@tecmalik 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! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Architectural Refactor: Split RefundVault into Factory and Policy Modules

2 participants