Skip to content

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

Description

@kilodesodiq-arch

Problem

The aid_escrow contract ships a complete delegate/recovery module, app/onchain/contracts/aid_escrow/src/delegate.rs, that is never compiled into the contract and therefore is absent from the ABI. src/lib.rs contains no mod delegate; declaration and the #[contractimpl] impl AidEscrow block exposes no set_delegate, get_delegate, or delegate-aware claim path. Because Rust only compiles modules that are declared, delegate.rs is dead code: cargo test in app/onchain never exercises it and the generated AidEscrowClient exposes none of its functions.

The module is also not merely unwired — it cannot be wired in as-is. It references a type that does not exist:

// app/onchain/contracts/aid_escrow/src/delegate.rs
let package: crate::AidPackage = env.storage()   // crate::AidPackage does not exist
    .persistent()
    .get(&package_key)
    .unwrap();

The actual package type in src/lib.rs is Package (with id, claim_starts_at fields that AidPackage omits), so compiling delegate.rs would fail immediately.

Consequence: the delegate/recovery capability the module documents — "Either the primary recipient OR the delegate may authorise a claim" — does not exist on-chain. The only claim paths are claim(id) (requires package.recipient.require_auth()) and claim_with_proof(id, claimant, proof) (Merkle membership). A recipient who loses their key, or a field operator claiming on a recipient's behalf, has no supported path; the "recovery" story is a comment, not a feature.

Root cause

The module was written against a hypothetical AidPackage shape and never reconciled with the real Package struct or the contract's claim flow, then orphaned rather than removed or completed.

Why this is architecturally hard

  1. It is a contract-state and auth-boundary change, not a cleanup. Delegates change who can move escrowed funds, so the feature must thread through finalize_claim (which currently does payout_recipient = package.recipient) and must be reconciled with claim_with_proof's Merkle path. A naive "declare mod delegate;" will not even compile because of the AidPackage mismatch.
  2. Two storage layouts exist. delegate.rs uses KEY_DELEGATES/KEY_DELEGATE_HISTORY/KEY_DELEGATE_EXPIRY as Map<u64, Address> entries in persistent storage, while package state lives under (Symbol "pkg", u64). The design must decide whether delegates are part of the Package record or a parallel map, and how clear_delegate/cleanup_expired_delegates interact with the existing get_recipient_package_count/list_recipient_packages full scans.
  3. Migration. The contract is already deployed on testnet (CDSBJ27PKTNFTRW6OKPCVXDRUSSRUIQUG6DW5PUTKLDXTDT23NQIS6JG). Adding an entrypoint is a new wasm + migrate()/redeploy decision under VERSIONING.md, not a source-only edit.
  4. Backend/tool coordination. If delegates are to be usable end-to-end, app/backend/src/onchain/onchain.adapter.ts (the OnchainAdapter interface) and tools/testnet-smoke/index.js must expose the new call(s); otherwise the contract change is untestable from the rest of the platform.

Proposed design

Decide first whether to complete or remove. The codebase already documents the intended semantics in delegate.rs, so completing is plausible: add a DelegateRecord-compatible field to Package (or keep the parallel map), add admin entrypoints set_delegate(package_id, delegate, expires_at) and view get_delegate(package_id), and make claim accept the delegate when is_authorised_claimer(...) is true (delegate not expired, package still Created). A markdown table of the new ABI surface:

Entrypoint Auth Notes
set_delegate(package_id, delegate, expires_at) admin reject if package Claimed
get_delegate(package_id) none return None if expired
claim (extended) recipient OR delegate delegate must not be expired

Downstream impact

Any new/changed entrypoint alters the Soroban ABI consumed by app/backend/src/onchain/soroban.adapter.ts (contract.call(method, ...)) and the deployment runbook docs/testnet-deploy-runbook.md. Removing delegate.rs instead has no ABI impact but closes the recovery path permanently; state that choice in the PR.

Acceptance criteria

Contract

  • cargo test in app/onchain compiles delegate.rs (or the file is deleted) — no module references a nonexistent type.
  • A registered, unexpired delegate can authorise a claim on a Created package, and the package transitions to Claimed exactly once.
  • A delegate cannot claim an expired package, a Claimed package, or after the delegate's own expires_at.
  • set_delegate is rejected for Claimed packages and when the delegate equals the recipient.

Tests

  • New tests in app/onchain/contracts/aid_escrow/tests/ cover: delegate claims, delegate expiry, delegate-after-claim rejection, and history/audit records.

Documentation

  • app/onchain/README.md method reference lists the new entrypoint(s) and its auth column, and the deployed testnet contract note reflects any redeploy.

Out of scope

Merkle-allowlist amount binding, recipient query pagination, and delegate UI in the mobile app are separate issues.

Getting started

Files: app/onchain/contracts/aid_escrow/src/lib.rs, app/onchain/contracts/aid_escrow/src/delegate.rs, app/onchain/contracts/aid_escrow/tests/.

cd app/onchain
make test        # cargo test -- --nocapture
make clippy      # cargo clippy -- -D warnings

Good first files to read: src/lib.rs (the claim/finalize_claim flow and Package struct), then src/delegate.rs to see the intended semantics you must reconcile against it.

Activity

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

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea:onchainOn-chain (Soroban) areabugSomething isn't workinghighHigh severity issues

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions