escrow: support multi-sponsor crowdfunding for a single issue_id - #63
Merged
chonilius merged 12 commits intoAug 16, 2026
Merged
Conversation
…MergeFi#57) Focused design analysis for MergeFi#57: the contribution model (fund creates, contribute appends), why refund needs exact per-contributor reimbursement rather than proportional splitting, the any-contributor extend_deadline semantics, and the MAX_SPONSORS bound rationale — written before the implementation that follows in subsequent commits.
Adds Contribution { sponsor, amount } and DataKey::Contribution(issue_id,
index) as the per-contributor storage shape (MergeFi#57), and a new
TooManySponsors error for the cap added in a later commit. Escrow gains
a contributor_count field; fund() is updated to set it to 1 for the
first (and, until the next commit, still only) contributor. The
contribution ledger itself isn't written yet — this is purely the data
model addition.
Adds MAX_SPONSORS (20) and starts writing to the contribution ledger: fund() now records its sponsor/amount as Contribution(issue_id, 0), treating the original funder uniformly as the first contributor rather than special-casing them relative to sponsors who join later via contribute() (added next).
The second half of the create/append split (MergeFi#57): fund() remains the sole entrypoint that creates an escrow (unchanged AlreadyFunded guard); contribute() is the new entrypoint every sponsor after the first uses to add more funds, capped at MAX_SPONSORS distinct contributions per escrow. Not yet wired into refund/extend_deadline — those still only know about the original single sponsor field, updated in the next commits.
…ponsor refund() now pays every contributor exactly their own recorded amount (MergeFi#57), rather than the full escrow.amount to a single escrow.sponsor. For a single-sponsor escrow this is exactly equivalent to the old behavior (contributor_count is always 1, so the loop runs once) — verified by the unmodified existing refund tests still passing. escrow. sponsor is still read by extend_deadline, updated in the next commit.
…e sponsor field Breaking API change (MergeFi#57): extend_deadline now takes an explicit caller parameter and accepts any address recorded as a contributor for that issue_id, rejecting non-contributors with Unauthorized — a direct generalization of the old escrow.sponsor.require_auth() rule to however many sponsors have now co-funded the escrow. With refund and extend_deadline both now working purely off the contribution ledger, Escrow's single sponsor field is redundant and removed; fund() no longer writes it. Updates the 4 existing extend_deadline tests for the new signature (auth still comes from the sponsor address, just passed explicitly now).
…contributors Lets off-chain callers (mergefi-backend, indexers) enumerate the full contribution ledger for an issue_id via 0..escrow.contributor_count, mirroring the existing get_escrow/get_admin/get_treasury getter pattern. Completes the implementation surface of MergeFi#57's design; remaining commits add test coverage and update docs.
Small doc-only follow-up to the crowdfunding implementation (MergeFi#57): fund()'s doc comment still described the old single-sponsor-only behavior; updates it to explain the AlreadyFunded/contribute split now that a second fund() call and a contribute() call mean different things.
…wdfunding (MergeFi#57) Adds the two core crowdfunding regression tests: three sponsors co-funding one issue_id with unequal amounts get back exactly their own contribution on refund (not an even split, not the full amount to one sponsor), and release() correctly pays out the combined total across all contributors, same fee math as a single-sponsor escrow.
…w, terminal status, MAX_SPONSORS cap (MergeFi#57) Five tests exercising contribute()'s validation surface: rejects without sponsor auth, rejects amount <= 0, rejects an unknown issue_id, rejects once the escrow is already Paid, and rejects the 21st distinct contribution once MAX_SPONSORS (20) is reached — the resource-bound guard the design doc calls out as addressing the same unbounded- collection concern raised elsewhere in this codebase (MergeFi#8/MergeFi#9).
…n, and get_contribution (MergeFi#57) Three tests: a second (non-original) contributor can successfully extend the shared deadline, a stranger who never contributed is rejected with Unauthorized even with valid auth for themselves, and get_contribution correctly enumerates each recorded contributor by index and returns EscrowNotFound past the last one.
…ution() and extend_deadline's new signature (MergeFi#57) Brings the README's escrow section back in sync with the implemented API: adds contribute and get_contribution to the function list and descriptions, updates extend_deadline's signature and description for the caller param and any-contributor semantics, and updates the Escrow/ Contribution data model block (dropped sponsor field, added contributor_count and the new Contribution struct).
|
@circleboyslimited is attempting to deploy a commit to the chonilius' projects Team on Vercel. A member of the Team first needs to authorize it. |
Contributor
|
all Ci passed. well done |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #57
escrow::fundpreviously accepted exactly onesponsor: Addressand rejected a secondfundcall against the sameissue_idoutright, so there was no on-chain way for more than one sponsor to co-fund the same issue. This adds that support.Design
Full reasoning in
docs/escrow-crowdfunding-design.md. Summary of the three decisions the issue asked to be made explicitly:fundstill creates the escrow (unchangedAlreadyFundedguard on a second call). A newcontribute(issue_id, sponsor, amount)is the entrypoint every sponsor after the first uses — it reuses the escrow's already-recorded token, so a top-up can't silently use a different asset. Each contribution is stored as its ownContribution { sponsor, amount }entry (DataKey::Contribution(issue_id, index)), the same per-entry shapemaintenance-pool::Depositalready uses, rather than one growingVeconEscrow.Contribution.sponsorexactly their ownContribution.amountback — no proportional-split math needed, since each contribution is stored as an exact amount rather than a share.caller: Addressand accepts any current contributor (not unanimous or contribution-weighted consent) — extending only ever delaysrefund's permissionless path, never redirects funds or changes anyone's share.recipients: Vec<(Address, u32)>inrelease/release_issuerisks resource-limit transaction failure #8/Unbounded growth ofMilestone.allocationsMap degrades and eventually threatens milestone usability #9):MAX_SPONSORS = 20capscontributor_count, boundingrefund's andextend_deadline's per-contributor loops to a small, predictable constant.Changes
contracts/escrow/src/types.rs:Escrowdropssponsor, gainscontributor_count: u32; newContributionstruct; newDataKey::Contribution(issue_id, index).contracts/escrow/src/error.rs: newTooManySponsorserror.contracts/escrow/src/lib.rs:fundrecords contribution index 0; newcontribute;refunditerates contributions;extend_deadlinetakes acallerparam and checks any-contributor membership; newget_contributionview getter.contracts/escrow/src/test.rs: 10 new tests (multi-sponsor fund/refund/release, contribute's auth/validation/cap guards, any-contributor extend_deadline + non-contributor rejection, get_contribution enumeration), plus updates the 4 pre-existingextend_deadlinetests for the new signature.README.md: escrow API reference and data model updated to match.Test plan
cargo test --workspace— 28/28 escrow (18 pre-existing + 10 new) + 7 maintenance-pool + 10 milestones, all passing.cargo clippy --workspace --tests— clean.cargo fmt --check --all— clean.