Skip to content

escrow: no crowdfunding support — a single issue_id can only ever be funded by one sponsor address #57

Description

@chonilius

Overview

escrow::fund accepts exactly one sponsor: Address and performs exactly one token_client.transfer(&sponsor, ..., &amount) (contracts/escrow/src/lib.rs:64-98), and — because a second fund call against the same issue_id is unconditionally rejected with AlreadyFunded regardless of who's calling (contracts/escrow/src/lib.rs:78-81) — there is no way, structurally, for more than one sponsor address to ever contribute to the same escrow. A high-value bounty that several interested parties would like to co-fund (a genuinely common real-world scenario for open-source bounty platforms — e.g. multiple companies wanting to jointly sponsor a critical fix, or a maintainer plus several community members pooling toward a larger reward) has no on-chain path: the current design forces either (a) picking one sponsor to front the entire amount and reimbursing them off-chain (defeating the point of an on-chain, trust-minimized escrow for everyone except the single fronting sponsor), or (b) using maintenance-pool instead, which does support multiple sponsors (contracts/maintenance-pool/src/lib.rs:57-110, no per-sponsor exclusivity check) but has none of escrow's single-issue deadline/refund guarantees — a pool isn't scoped to one issue_id at all, so "crowdfund this one issue" isn't actually what a maintenance pool models.

This is a real product/architecture gap, not a bug in existing behavior — but it's a substantial one given the platform's stated purpose ("MergeFi lets sponsors fund open-source work," plural sponsors implied throughout the README's framing, e.g. "team-splits/milestones" already acknowledge multi-party payouts, but nothing acknowledges multi-party funding).

The core design difficulty, beyond simply accepting more than one transfer call: refund's proportionality. refund currently pays escrow.amount in full, unconditionally, to the single escrow.sponsor address (contracts/escrow/src/lib.rs:168-173). If multiple sponsors co-funded an escrow that later expires unresolved, a correct refund needs to return each sponsor's own contributed amount to their own address — not the full amount to whichever address happens to be recorded as "the" sponsor. This requires tracking a list of (sponsor, amount) contributions per issue_id (structurally similar to maintenance-pool's per-pool Deposit records, contracts/maintenance-pool/src/types.rs:21-25) rather than the current single sponsor: Address field, and a refund implementation that iterates and repays each contributor proportionally — itself vulnerable to the same "unbounded collection" concern #8/#9 already raise for recipients/allocations, now applying to the funding side of escrow for the first time.

Requirements

  • Design a multi-sponsor contribution model for escrow: likely a Vec<(Address, i128)> or similar per-issue_id contribution ledger, replacing (or supplementing) the single sponsor: Address field, tracking who contributed how much.
  • Design proportional refund logic: on refund (either admin-early or permissionless-post-deadline), each contributor gets back exactly what they put in — no rounding advantage/disadvantage to any one contributor, mirroring the same largest-remainder-style care compute_split already applies to payouts, now needed on the funding side too.
  • Decide how extend_deadline should behave with multiple sponsors — today it's gated by the single sponsor.require_auth(); with multiple contributors, does any contributor get to extend, does it require unanimous consent, or majority-by-contribution-weight? This needs an explicit, reasoned answer, not a default.
  • Evaluate the same unbounded-collection/resource-limit concern Unbounded recipients: Vec<(Address, u32)> in release/release_issue risks resource-limit transaction failure #8/Unbounded growth of Milestone.allocations Map degrades and eventually threatens milestone usability #9 raise for other growing collections, applied here to the contribution list.

Acceptance Criteria

  • Design decision documented for the contribution model, proportional refund logic, and multi-sponsor extend_deadline semantics
  • Implementation: escrow supports N sponsors co-funding one issue_id up to the total amount, with correct proportional refund
  • Tests: multiple sponsors fund the same issue, escrow expires unresolved, refund correctly returns each sponsor's own contribution to their own address (not just an even split, and not the full amount to only one of them)
  • cargo test --workspace passes

Additional Notes

  • Precise references: fund's single-sponsor, single-transfer design at contracts/escrow/src/lib.rs:64-98; refund's full-amount-to-single-sponsor payout at contracts/escrow/src/lib.rs:168-173; maintenance-pool::deposit's multi-sponsor pattern (contracts/maintenance-pool/src/lib.rs:57-110) as a partial structural precedent for the contribution-ledger shape, though maintenance-pool never needs to repay individual sponsors proportionally the way this issue's refund logic would, since it has no refund concept at all (see the timeout-escape-hatch issue filed elsewhere in this batch).
  • This is a genuinely different, harder design problem than the companion milestones crowdfunding issue filed alongside this one — escrow's proportional-refund-of-a-single-fixed-amount is more contained than milestones' case, where a shared total_budget gets allocated across many issues before any of it is spent, requiring proportional accounting to track through allocation and partial-cancellation too, not just a single refund event.
  • Cross-references: the companion milestones crowdfunding issue (harder variant of the same underlying request); Unbounded recipients: Vec<(Address, u32)> in release/release_issue risks resource-limit transaction failure #8/Unbounded growth of Milestone.allocations Map degrades and eventually threatens milestone usability #9 (unbounded collection growth — the same category of resource concern, now applying to a new collection this issue's design would introduce); the permanent-issue_id-retirement issue filed in this batch (a co-funded escrow's product semantics for "can this issue_id be crowdfunded again after a refund" should be decided consistently with that issue's resolution).

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignarchitectureArchitecture/design issueenhancementNew feature or requestvery hardVery difficult task, expert-level effort required

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions