Skip to content

feat(onchain/aid_escrow): wire delegate module for recovery claims - #443

Merged
kilodesodiq-arch merged 1 commit into
ChainForgee:mainfrom
Magrexy:feat/issue-422-delegate-claims
Aug 20, 2026
Merged

feat(onchain/aid_escrow): wire delegate module for recovery claims#443
kilodesodiq-arch merged 1 commit into
ChainForgee:mainfrom
Magrexy:feat/issue-422-delegate-claims

Conversation

@Magrexy

@Magrexy Magrexy commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #422

src/delegate.rs referenced a nonexistent crate::AidPackage type and was never compiled into the contract — lib.rs had no mod delegate;, so the entire delegate/recovery module was dead code, absent from the ABI, and never exercised by cargo test. This PR reconciles the module against the real Package/PackageStatus types, wires it into AidEscrow, and threads delegate authorisation through the claim path. The key design decision is that delegate state lives in parallel maps keyed by package id (dlgts, dlgexp, dlgh), not inside the Package record, so the persisted Package shape is unchanged and no storage migration is required.

Why

Before this change the only claim paths were claim(id) (requiring package.recipient.require_auth()) and claim_with_proof (Merkle membership). A recipient who lost their key — or a field operator claiming on a recipient's behalf — had no supported path; the "recovery" story was a doc comment, not a feature. A naive mod delegate; would not even compile, because the module referenced crate::AidPackage, which does not exist (lib.rs defines Package with id and claim_starts_at). This PR completes the module against the real types and the real finalize_claim flow rather than deleting it, preserving the recovery path the module already documented.

What was built

app/onchain/contracts/aid_escrow/src/:

File What it contains
delegate.rs Delegate/recovery logic reconciled to crate::Package. Persistent keys: dlgts (Map<u64, Address>), dlgexp (Map<u64, u64>), dlgh (Vec). DelegateHistory.new_delegate is now Option<Address> so a cleared delegate is auditable. Functions: is_authorised_claimer, set_delegate_with_expiry, get_delegate, get_delegate_info, get_delegate_history, clear_delegate, cleanup_expired_delegates. 10 in-file unit tests.
lib.rs Declares mod delegate;, exposes the five entrypoints (set_delegate, get_delegate, get_delegate_info, get_delegate_history, cleanup_expired_delegates), changes claim(env, id)claim(env, id, claimer), and finalize_claim now takes an actor and clears the delegate after a successful claim.

app/onchain/contracts/aid_escrow/tests/:

File What it contains
delegate_tests.rs 14 new integration tests covering delegate claims, exactly-once transition, delegate expiry, after-claim rejection, set_delegate rejections, and history/audit records.
existing tests/*.rs Mechanical updates of claim(&id)claim(&id, &recipient) for the new signature.

Integration changes outside the module

  • src/lib.rsclaim ABI changed (claim(id)claim(id, claimer)); finalize_claim gained an actor parameter and calls delegate::clear_delegate. Unavoidable: delegate authorisation must thread through the claim entrypoint.
  • scripts/claim.sh, scripts/testnet-invoke.sh — claim invocations now pass --claimer.
  • app/onchain/README.md, contracts/aid_escrow/README.md — method reference and redeploy note.
  • test_snapshots/ — tracked snapshots regenerated for the new claim ABI; new snapshots added for the delegate tests.

Acceptance criteria coverage

  • cargo test in app/onchain compiles delegate.rs (or the file is deleted) — no module references a nonexistent type. (mod delegate; in lib.rs; delegate.rs uses crate::Package; compiles and its 10 unit tests run.)

  • A registered, unexpired delegate can authorise a claim on a Created package, and the package transitions to Claimed exactly once. (delegate_tests.rs::registered_unexpired_delegate_can_claim_on_behalf_of_recipient, package_transitions_to_claimed_exactly_once)

  • A delegate cannot claim an expired package, a Claimed package, or after the delegate's own expires_at. (delegate_cannot_claim_an_expired_package, delegate_cannot_claim_after_recipient_already_claimed, delegate_cannot_claim_after_own_expiry)

  • set_delegate is rejected for Claimed packages and when the delegate equals the recipient. (set_delegate_is_rejected_for_claimed_packages, set_delegate_is_rejected_when_delegate_equals_recipient)

  • New tests in app/onchain/contracts/aid_escrow/tests/ cover: delegate claims, delegate expiry, delegate-after-claim rejection, and history/audit records. (tests/delegate_tests.rs — 14 tests; audit records asserted in set_delegate_replaces_previous_and_audits_both_assignments and registered_unexpired_delegate_can_claim_on_behalf_of_recipient)

  • app/onchain/README.md method reference lists the new entrypoint(s) and its auth column, and the deployed testnet contract note reflects any redeploy. (method table lists claim(id, claimer), set_delegate, get_delegate, get_delegate_info, get_delegate_history, cleanup_expired_delegates with auth; redeploy note added.)

Deliberately deferred

  • Backend/tool coordination — the issue lists app/backend/src/onchain/onchain.adapter.ts and tools/testnet-smoke/index.js as downstream impact. Those are separate platform layers and will be a follow-up PR; the contract change is independently invokable via scripts/testnet-invoke.sh / soroban contract invoke.
  • Testnet redeploy — this PR ships the new wasm source and documents the redeploy requirement, but does not perform the redeploy itself. The currently deployed contract (CDSBJ27…) does not yet expose the new entrypoints.

Test plan

  • cargo test (in app/onchain) — 181/181 passing (14 new integration tests in tests/delegate_tests.rs; 10 unit tests in src/delegate.rs, previously uncompiled)
  • cargo fmt --all -- --check — clean
  • cargo clippy --all-targets -- -D warnings — no warnings
  • cargo build --release --target wasm32-unknown-unknown — succeeds
  • Manual: testnet redeploy then delegate-claim smoke (blocked on redeploy)

Env vars / Notes

No new env vars or config keys. The claim ABI change is breaking: claim(id)claim(id, claimer). Funds always pay out to package.recipient, even when a delegate authorises the claim. Delegate state uses parallel maps, so existing Package records remain readable with no migration. A redeploy (new wasm) is required before the new entrypoints are live on testnet.

…audit trail (ChainForgee#422)

Add an admin-managed delegate (recovery) address per package. An
unexpired delegate may authorise a claim on the recipient's behalf,
with funds always paid out to the recipient.

- `claim` now takes a `claimer` address and requires its auth; the
  recipient remains authorised.
- New entrypoints: set_delegate, get_delegate, get_delegate_info,
  get_delegate_history, cleanup_expired_delegates.
- Delegate state lives in parallel maps keyed by package id, so the
  persisted Package shape is unchanged and no migration is needed.
- Assignments are rejected for claimed packages and when the delegate
  equals the recipient; every change is appended to an audit trail.
- Updated claim scripts and README for the new claim signature.
- Regenerated test snapshots to match the new claim ABI.

@kilodesodiq-arch kilodesodiq-arch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great Job Sir/Ma

@kilodesodiq-arch
kilodesodiq-arch merged commit bd28e45 into ChainForgee:main Aug 20, 2026
5 checks passed
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.

aid_escrow delegate module is dead code: delegate and recovery claims cannot be made

2 participants