Skip to content

feat(contract): add linear vesting streams - #155

Open
Ezekiel146 wants to merge 14 commits into
DocForum:mainfrom
Ezekiel146:fix/issue-44
Open

Ezekiel146 wants to merge 14 commits into
DocForum:mainfrom
Ezekiel146:fix/issue-44

Conversation

@Ezekiel146

Copy link
Copy Markdown
Contributor

Summary

  • Add streamed escrow creation with start/end and optional cliff timestamps, checked integer vesting, payee withdrawals, and a vesting read method.
  • Withdrawals round down at intermediate timestamps and cannot exceed the funded amount; at end, the full amount is vested.
  • A streamed refund closes atomically by paying vested-but-unwithdrawn funds to the payee and returning the unvested remainder to the payer. Ordinary escrow behavior remains unchanged.
  • Add boundary, cliff, cumulative withdrawal, invalid-schedule, and midstream refund regression tests; document the decisions in ADR 0007.

Validation: git diff --check passed. Tests/build/install were not run per contributor workflow. Generated SDK bindings and testnet deployment remain pending.

Closes #44

@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

@Ezekiel146 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146

This PR has merge conflicts with main, which has moved on since this branch was created. Please rebase onto the latest main (or merge main into your branch), resolve the conflicts, and push. CI will then re-run, and the PR can be merged once it's conflict-free, CI is green and it's been reviewed. Thanks!

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146 Thanks for the PR. It can't be merged yet. To unblock it:

Merge conflicts: this branch conflicts with main. Please rebase onto the latest main (git fetch upstream && git rebase upstream/main), resolve the conflicts (often ROADMAP.md, ARCHITECTURE*.md or prisma/schema.prisma), and force-push.

Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge.

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 20, 2026
# Conflicts:
#	ARCHITECTURE_ESSENTIALS.md
#	README.md
#	ROADMAP.md
#	contracts/escrow/src/lib.rs
#	docs/threat-model.md
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber I resolved the conflicts with main, integrated streamed vesting with the current quorum, allowlist, and settlement paths, and refreshed the generated contract bindings. The focused streamed-vesting tests pass. Please review and merge when ready.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146 Thanks for merging main in. The malware scan passed, but the PR still has merge conflicts with main, because more escrow PRs landed after your merge (#183 release callback, on top of #203 M-of-N quorum). Please update again:

git fetch upstream
git merge upstream/main     # or: git rebase upstream/main
# resolve the conflicts (usually contracts/escrow/src/lib.rs, the create_escrow
# call sites in tests, ROADMAP.md and ARCHITECTURE_ESSENTIALS.md), then:
cargo test                              # commit any updated test_snapshots/
./scripts/check-generated-bindings.sh   # regenerate the SDK bindings if it fails
git push

Once CI is green and the conflicts are gone, it can be merged.

@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber I resolved the conflict with current main, preserved the stream feature alongside the newer callback and quorum code, and renumbered its ADR to 0008 to avoid colliding with ADR 0007. The full contract suite and generated binding check pass. The description links Closes #44; please review and merge when ready.

@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #196 (feat(sdk): fee estimation and fee-bump sponsorship helpers) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

# Conflicts:
#	contracts/escrow/src/lib.rs
#	sdk/src/generated/contract-client.ts
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

The branch now includes the latest main and the merge conflicts are resolved; the streamed release behavior and regenerated contract bindings are retained. The PR description still includes Closes #44. @afeezorobsco-cyber, please review the updated PR and merge it once the checks pass.

@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #158 (feat(contract): sponsored funding: a funder distinct from the refund …) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

Soroban contract (test + wasm build)

cargo test changed committed Soroban snapshots under contracts/escrow/test_snapshots.

Your change alters contract behaviour that the committed Soroban snapshots record. Run cargo test in contracts/escrow, review the diff under contracts/escrow/test_snapshots/, and commit the updated snapshots.

This PR also conflicts with main again, so please rebase first, then fix the above.

@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber I resolved the merge conflicts against the latest main, preserved this PR’s feature, regenerated the contract bindings, and updated the affected test snapshots. The escrow contract tests pass. Could you please review the update and merge when ready?

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 21, 2026
@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

SDK bindings match contract WASM

sdk/src/generated/contract-client.ts is out of date.

sdk/src/generated/contract-client.ts doesn't match the contract any more, because main has changed the contract since your branch was created. Regenerate the bindings against the latest main and commit them:

cd contracts/escrow && cargo build --target wasm32v1-none --release
stellar contract bindings typescript --wasm target/wasm32v1-none/release/docforum_escrow.wasm --output-dir /tmp/escrow-bindings --overwrite
cp /tmp/escrow-bindings/src/index.ts ../../sdk/src/generated/contract-client.ts

(scripts/check-generated-bindings.sh runs the same check locally.)

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 21, 2026
@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #150 (feat(contract): add two-party releaser rotation) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

Resolved conflicts in ARCHITECTURE_ESSENTIALS.md, ROADMAP.md, and
contracts/escrow/src/lib.rs, keeping main's behaviour (two-party releaser
rotation, pooled escrows, release-not-before, etc.) and the linear
vesting stream feature from this branch.

- VestingSchedule/VestingInfo types, DataKey::Vesting/Withdrawn, and the
  new errors (PoolInsufficient=35, NotStreamed=36,
  InvalidVestingSchedule=37, NothingToWithdraw=38,
  StreamTopUpUnsupported=39) are appended after main's existing codes
  (1-34), so no existing variant's discriminant changed.
- create_streamed_escrow, get_vesting, and withdraw are added as new
  entry points alongside main's create_escrows/create_pooled_escrow/
  rotate_releaser.
- settle() now branches on an optional vesting schedule: a release pays
  the payee whatever is still unwithdrawn; a refund (or payer cancel)
  pays the vested-but-unwithdrawn amount to the payee and the unvested
  remainder to the stored refund_to. Non-streamed escrows are unchanged.
- top_up rejects streamed escrows (StreamTopUpUnsupported), since a
  stream's total is fixed at creation.
- Moved the vesting ADR to docs/adr/0012 (0008 was taken by main's
  payee-acceptance ADR) and cross-referenced refund_to/ADR 0009.
- Added changelog.d/44-streamed-vesting.md instead of editing
  ROADMAP.md's Changelog section (changelog.d/README.md convention).
- Took main's generated sdk/src/generated/contract-client.ts for now;
  regenerating from the merged contract is the next step.
Regenerated sdk/src/generated/contract-client.ts with stellar CLI 28.1.0
against the merged contract WASM, adding create_streamed_escrow,
withdraw, get_vesting, VestingSchedule/VestingInfo, and errors 35-39.
scripts/check-generated-bindings.sh confirms the committed file matches.
Merging main added a dedicated DataKey::Status(id) storage entry (for
get_status) that the vesting branch's escrows did not previously write.
Re-running cargo test after the merge regenerates the six streamed-
vesting snapshots with that entry; no other content changed.
Same cause as the escrow snapshot update: main added DataKey::Status(id)
for get_status, which now shows up in this cross-crate integration
test's captured storage. Re-ran cargo test twice; only this one
snapshot changes and it is byte-identical on the second run.
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

Merged origin/main (28ec877) into this branch and resolved the conflicts — commit 84abb7d. Kept main's behaviour (M-of-N quorum, pooled escrows, sponsored refunds, release-not-before, payer cancel, releaser rotation, etc.) and the linear vesting stream feature from this PR side by side:

  • VestingSchedule/VestingInfo, DataKey::Vesting/Withdrawn, and the new errors (PoolInsufficient=35, NotStreamed=36, InvalidVestingSchedule=37, NothingToWithdraw=38, StreamTopUpUnsupported=39) are appended after main's codes 1–34 — no existing error's discriminant changed, no duplicates.
  • settle() now branches on whether the escrow has a vesting schedule: release pays the payee whatever is still unwithdrawn; refund (and the payer's pre-acceptance cancel) pays the vested-but-unwithdrawn part to the payee and returns the unvested remainder to the stored refund_to. Non-streamed escrows are byte-for-byte unchanged.
  • Moved the vesting ADR to docs/adr/0012 (0008 had since been taken by main's payee-acceptance ADR) and cross-referenced refund_to/ADR 0009 in its refund-path paragraph.
  • Added changelog.d/44-streamed-vesting.md instead of touching ROADMAP.md's ## Changelog section, per changelog.d/README.md; ticked the Phase E5 roadmap line as its own added line, not a rewrite of neighbouring ones.

Bindings. Regenerated sdk/src/generated/contract-client.ts with the pinned stellar CLI 28.1.0 against the merged contract WASM (commit 845c1a3). scripts/check-generated-bindings.sh now passes — this is what CI was failing on.

Tests. cargo test in contracts/escrow: 139 tests pass. Merging in main's DataKey::Status(id) key changed six streamed-vesting snapshots and one releaser-account cross-crate snapshot (storage now includes a Status entry that streamed escrows didn't previously write) — reviewed, intentional, committed (b2e98d7, 80af89c). Ran cargo test a second time in both crates: zero diff, snapshots are deterministic. Also ran the bounded proptest state-machine suite (cargo test --test model, 4 tests) and contracts/releaser-account's suite — all pass.

CI checks run locally:

  • cargo build --target wasm32v1-none --release (both crates) — OK.
  • scripts/check-generated-bindings.sh — OK.
  • python3 scripts/check-resource-baseline.py — OK (13 profiled functions, unaffected by this change).
  • SDK: npm run typecheck, npm run build, npm run test:offline, test:milestones, test:unit, test:runtime — all pass (the SDK wrapper wasn't touched; only the generated client changed, additively).
  • cargo clippy --all-targets: only warnings, no errors; there's no clippy CI job in this repo. The "too many arguments" and "needless-reference" warnings are pre-existing on main (large existing creation functions and macro-expanded #[contractimpl] code) — nothing new from this PR's own functions.
  • cargo fmt --check: drift exists, but entirely in files this PR doesn't touch (benches/resource_profile.rs, tests/model.rs, tests/pooled.rs, plus a few lines elsewhere in lib.rs outside the vesting code) — pre-existing on main, not introduced here. The new streamed_vesting.rs test file and the new vesting functions in lib.rs are already fmt-clean.
  • sdk/tests/batch.test.ts (not in CI's script list) fails with 6 errors on plain origin/main too (createEscrows/settleMany aren't implemented in the SDK wrapper yet) — confirmed pre-existing, unrelated to this PR.

Diff vs origin/main is now a clean 15 files / +6,665 −9: the vesting contract code, its test file and six snapshots, the ADR, one ARCHITECTURE_ESSENTIALS.md/ROADMAP.md line each, the changelog fragment, the regenerated bindings, and one releaser-account snapshot. No unrelated files, no generated clutter, no secrets.

One pre-existing gap worth flagging (not from this merge): README.md's contract-surface summary and docs/threat-model.md's invariant notes don't mention streams — this branch's edits to those two files were already gone by the time I started (lost in an earlier merge on this branch, before origin/main even added Status/pooled/etc.). ARCHITECTURE_ESSENTIALS.md (the single source of truth per AGENTS.md) does list the three new functions and a short paragraph. Happy to add one sentence to each of README.md/threat-model.md if you'd like that restored, but held off since it's outside a merge conflict and touches shared lines other open PRs may also be touching.

@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber this is ready for a look whenever you have a moment.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 26, 2026
@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 27, 2026
@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #153 (feat(contract): expose version and build provenance) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@zeemscript

Copy link
Copy Markdown

@afeezorobsco-cyber I updated this branch to current main and aligned streamed withdrawals/settlement with outstanding liabilities. Full escrow contract tests, generated-binding verification, and resource-baseline checks pass locally; the malware scan also passes. GitHub reports this PR as mergeable. Please review/approve the newly updated head and CI. This branch uses version 2.4.0 and assumes #154 (2.3.0) lands first; please merge #154 before #155 to keep the version sequence.

@github-actions github-actions Bot added needs-rebase Has merge conflicts with main; the author needs to rebase and removed needs-rebase Has merge conflicts with main; the author needs to rebase labels Oct 27, 2026
@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #154 (fix(contract): keep failed releases recoverable) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Ezekiel146 Heads-up: #268 just changed how contract PRs work in this repo. Your PR changes contracts/escrow/src/lib.rs, so CI's new "Contract changes use a fragment, not a version bump" step will fail it until you make these changes (details in contract-changes.d/README.md):

  1. Revert your CONTRACT_VERSION change. This branch sets 2.4.0. Leave the line exactly as it is on main (currently 2.3.0). A maintainer bumps it once per batch now, so nobody picks the same "next" version any more.

  2. Add a fragment, e.g. contract-changes.d/44-feat-contract-add-linear-vesting-streams.md:

    bump: minor
    ---
    Issue #44: one-paragraph summary of the contract change.

    Use patch for a behaviour fix with no interface change, minor for new entry points, arguments, events or errors, and major for breaking changes.

  3. Switch your new Error codes to provisional ones (issue number × 100 + n). This branch adds PoolInsufficient = 37, NotStreamed = 38, InvalidVestingSchedule = 39, NothingToWithdraw = 40, StreamTopUpUnsupported = 41; use PoolInsufficient = 4400, NotStreamed = 4401, InvalidVestingSchedule = 4402, NothingToWithdraw = 4403, StreamTopUpUnsupported = 4404. The fold assigns the final numbers. Don't renumber existing codes.

This should also make your next merge from main much easier: the version line was the one thing every contract PR conflicted on.

# Conflicts:
#	ROADMAP.md
#	contracts/escrow/src/lib.rs
#	contracts/escrow/tests/create_escrow.rs
#	sdk/src/generated/contract-client.ts
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

Synced this branch with current main and resolved the merge conflict. Kept CONTRACT_VERSION at 2.3.0, added the required issue #44 minor fragment with provisional stream error codes 4400–4404, and regenerated the SDK binding. Full Rust tests, the contract-change check, and generated-binding check pass. GitHub reports mergeable=true; Malware scan passed. @afeezorobsco-cyber, please recheck when convenient.

@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #199 (feat(contract): payee rights transfer: payee reassigns future payout to a new address) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-escrow.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

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

Labels

needs-rebase Has merge conflicts with main; the author needs to rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase E5] Streamed (linear vesting) release between two timestamps

3 participants