Skip to content

docs(fundraising): group fundraising escrow design spec - #131

Open
Douglasacost wants to merge 3 commits into
mainfrom
docs/group-fundraising-design
Open

docs(fundraising): group fundraising escrow design spec#131
Douglasacost wants to merge 3 commits into
mainfrom
docs/group-fundraising-design

Conversation

@Douglasacost

@Douglasacost Douglasacost commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

What

Design-only pass for the Groups feature: a group creates an objective (a target amount and a deadline), members deposit toward it, and the escrow resolves to exactly one of two outcomes — the beneficiary is paid, or every member takes their own money back.

No contract, no tests, nothing deployed. This PR is one markdown file plus the spell-check words it needs.

Decisions recorded

Model — all-or-nothing with a goal latch. A member may withdraw their own deposit while the objective is below its target; that exit closes permanently once the target is reached. Free withdrawal right up to the deadline would let a met goal be unwound at the last second; locking from day one commits a member's money for months with no individual undo. Cutting the exit at the goal gives members a real way out while the group is still deciding, and gives the group certainty the instant it succeeds.

Resolution is permissionless. Once the goal is met or the deadline passes, anyone can finalize, and every member pulls their own funds. No role, signature, or organizer cooperation can freeze member money — this is the property the whole design is built around.

Lineage — blueprint, not dependency. Nothing in this space is importable: OpenZeppelin removed its escrow contracts in 5.0.0, Party Protocol has wound down, and no ERC standard for crowdfunding escrow was ever adopted. So:

  • Shape — Solidity by Example's CrowdFund (MIT), among the most widely copied crowdfunding contracts in the community and the same state machine as OpenZeppelin's removed RefundEscrow, reached independently a decade apart.
  • Substance — OpenZeppelin primitives (SafeERC20, ReentrancyGuard, AccessControl, EIP712, SignatureChecker). This is what we actually import.
  • Adversary — Party Protocol, read-only. It has the deepest published review history for this contract shape (a 0xMacro audit plus several Code4rena engagements), so its published findings become our test cases. In particular, its finalization-DoS finding (Code4rena Oct 2023, M-06) is carried into the threat model as a named regression test.

Membership is backend-signed. Groups stay off-chain; single-use EIP-712 authorizations with explicit nonces gate objective creation and deposits.

Gas

§8 reflects ERC20FeePaymaster (#127). It is destination-agnostic — the off-chain erc20-fee-signer signs (from, to, token, amount, expiry, maxFeePerGas, gasLimit) — so serving this escrow needs no change to the paymaster and no change to the escrow, only that the signer's policy covers it. Pricing happens off-chain, so there is no on-chain rate and no oracle.

No feature-specific paymaster is introduced. The escrow never assumes one exists: every function works from an ordinary self-paying transaction, which is what keeps finalize, unpledge, and refund reachable regardless of gas infrastructure.

Scope

This feature deploys new contracts only. It modifies no deployed contract, requires no token migration, and needs no change to any live paymaster — nothing currently in production is touched. Anything that would require altering an existing deployment is out of scope by definition, which is what makes this shippable independently of everything else.

Open questions in the doc

All concern the new contract only. Immutable vs. upgradeable; whether keep-what-you-raise is a real product case; protocol fee on/off; overshoot past the goal; and whether the erc20-fee-signer policy should cover this escrow — the last being off-chain configuration rather than a contract change, and not a launch blocker, since without it members simply pay their own gas.

Design-only pass for the Groups feature: a group creates an objective,
members deposit toward it, and the escrow resolves to exactly one of two
outcomes - the beneficiary is paid, or every member takes their money back.

Model is all-or-nothing with a goal latch: a member may withdraw their own
deposit while the objective is below its target, and that exit closes
permanently once the target is reached. Resolution is permissionless so no
role, signature, or organizer cooperation can freeze member funds.

Shape follows Solidity by Example's CrowdFund (the same state machine as
OpenZeppelin's removed RefundEscrow), built on OpenZeppelin primitives, with
Party Protocol's audit findings carried into the threat model as test cases.

No contract, no tests, nothing deployed. Adds domain terms to .cspell.json.
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

LCOV of commit dce5a96 during checks #766

Summary coverage rate:
  lines......: 26.5% (939 of 3550 lines)
  functions..: 27.0% (150 of 555 functions)
  branches...: 27.1% (164 of 605 branches)

Files changed coverage rate: n/a

…tive

Factual corrections found while verifying every citation against source:

- OpenZeppelin removed Escrow/ConditionalEscrow/RefundEscrow in 5.0.0, not
  4.0 (vendored CHANGELOG; the contracts survived through 4.9). Corrected in
  three places plus the Sources entry.
- Juicebox has not wound down. V4 shipped April 2025 and the protocol is live
  with active TVL. Its model is rejected on its own merits, not for lack of a
  maintainer. Party Protocol, Gitcoin Allo and Mirror had wound down as stated.
- Party Protocol's review history is a 0xMacro audit plus several Code4rena
  engagements, not two; "the only serious audit history in this space" was
  overstated and is now scoped to this contract shape.
- The Code4rena finalization finding locks funds until expiry rather than
  permanently, and is now cited as M-06 to avoid colliding with this repo's
  PR #127.
- Solidity by Example's CrowdFund is MIT-licensed; say so instead of leaving
  it as an open question.

Consistency and fitness:

- minContribution/maxTotalContributions were listed as backend-only policy
  while also being on-chain fields signed into the creation authorization.
  They are contract-enforced on deposit and never on finalize.
- Dropped IGroupFundraisingGaslessValidator from the file layout, which
  contradicted the conclusion that no validator hook is needed.
- Fixed the stale docs/ path in the file layout.
- Removed version banners, decision-log lines and references to earlier
  drafts so the document reads as a standalone specification.
The feature deploys new contracts only: it modifies no deployed contract,
requires no token migration, and needs no change to any live paymaster.
That constraint is now stated in the product framing rather than left
implicit, and the open decisions are scoped to respect it.

Removes the "add ERC20Permit to L2 NODL" open question. It would collapse
every NODL deposit to one transaction, but it means changing a token already
in production, so it is out of scope by definition. NODL deposits use the
two-step approve path; the escrow still gains the single-transaction path
automatically for any permit-capable token it is given.

Clarifies that the erc20-fee-signer question is off-chain configuration only
and not a launch blocker, since without it members simply pay their own gas.

Also fixes the minContribution/maxTotalContributions contradiction that the
previous commit intended to correct but did not apply: those are on-chain
fields signed into the creation authorization and enforced on deposit, never
on finalize.
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.

1 participant