Skip to content

fix(contract): keep failed releases recoverable - #154

Merged
afeezorobsco-cyber merged 16 commits into
DocForum:mainfrom
Ezekiel146:fix/issue-55
Oct 28, 2026
Merged

afeezorobsco-cyber merged 16 commits into
DocForum:mainfrom
Ezekiel146:fix/issue-55

Conversation

@Ezekiel146

@Ezekiel146 Ezekiel146 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Use Soroban's fallible token client for outbound release transfers.
  • Map failed payouts to Error::Undeliverable while keeping the escrow funded so the releaser can retry or use the existing refund path.
  • Add regression tests for an unauthorized SAC trustline and a receiver contract panic.
  • Update the generated SDK error map and relevant documentation.

Closes #55

Verification

  • cargo test --manifest-path contracts/escrow/Cargo.toml -- --test-threads=1 passes.
  • cargo fmt --manifest-path contracts/escrow/Cargo.toml --check passes.

@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:

CI is failing. Here's what broke:

Soroban contract (test + wasm build):

test unauthorized_sac_payee_leaves_escrow_funded_and_can_be_retried ... FAILED
thread 'unauthorized_sac_payee_leaves_escrow_funded_and_can_be_retried' (4606) panicked at /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/soroban-env-host-27.0.1/src/host.rs:910:9:
HostError: Error(Contract, #2)
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

Please fix these errors and run the same steps locally before pushing (npm run typecheck && npm test && npm run build, or cargo test for contracts).

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
#	contracts/escrow/tests/create_escrow.rs
#	contracts/escrow/tests/malicious_token.rs
#	contracts/escrow/tests/timeout_refund.rs
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber I merged the latest main, resolved the conflicts, and fixed the failing undeliverable payout fixture. The full escrow Rust test suite and formatting check pass. Please review and merge when ready.

@Ezekiel146

Copy link
Copy Markdown
Contributor Author

@afeezorobsco-cyber This branch now includes the latest main and is conflict-free. The full escrow Rust test suite and formatting check pass. Please review and merge when ready.

@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:

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.)

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.

@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 24, 2026
@github-actions

Copy link
Copy Markdown

@Ezekiel146 This PR now has merge conflicts with main, most likely because #211 (feat(security): audit-readiness package for the Phase E5 contract features) 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 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 what the committed snapshots record. Run cargo test in contracts/escrow on top of the latest main, review the diff under contracts/escrow/test_snapshots/, and commit the updated snapshots (including any new .json files).

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.

@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.

The Fail if committed test snapshots changed step means your change alters what the committed Soroban snapshots record. Run cargo test in contracts/escrow on top of the latest main, review the diff under contracts/escrow/test_snapshots/, and commit the updated snapshots (including any new .json files).

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

Ezekiel146 and others added 2 commits October 24, 2026 13:31
Resolve conflicts with main (d74b961):

- contracts/escrow/src/lib.rs: main added InvalidReleaser = 24. Keep it and
  move Undeliverable to 35, the next free code, so existing discriminants
  keep their values.
- sdk/src/generated/contract-client.ts: take main's file, then regenerate it
  from the merged contract WASM with stellar CLI 28.1.0
  (scripts/check-generated-bindings.sh passes).
- ARCHITECTURE_ESSENTIALS.md, docs/threat-model.md: keep both sides.
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

Note for maintainers: the merge with main moved Undeliverable to error code 35, because main's InvalidReleaser keeps 24. The bindings check was failing on this branch and is regenerated. The GitHub title is 'fix(contract): keep failed releases recoverable', not the one in the review list, so please confirm the intended title. The fork's workflow run needs maintainer approval.

The status-read optimisation on main keeps a separate DataKey::Status
entry next to the escrow record. Regenerate the two undeliverable payee
snapshots so they include it: Funded after the rejected release, and
Released after the retry.
Picked up from merging main's status-read change (DocForum#260): releaser-account's
own fixture now also has a separate DataKey::Status entry. Not touched by
this PR's change; regenerated by running its test suite.
@Ezekiel146

Copy link
Copy Markdown
Contributor Author

Brought this up to date with main and addressed the CI feedback.

Update, not a rebase: merged origin/main (28ec877) into fix/issue-55 rather than rebasing, so the already-pushed history stays intact — git merge origin/main → 95f0352.

Conflicts and resolutions

  • docs/threat-model.md — the only real conflict. Both sides added a line in the same spot (this PR's Undeliverable note vs. main's unrelated T4 heading moving down). Kept both: the Undeliverable sentence stays, immediately above the T4 intermediate-contract re-entry section header from main.
  • ARCHITECTURE_ESSENTIALS.md, contracts/escrow/src/lib.rs — auto-merged cleanly; main's unrelated changes (formal-verification doc comment, #101) and this PR's try_transfer/Undeliverable branch in settle() don't overlap.
  • ROADMAP.md — no conflict (this PR had no entry there yet). Added one line under Phase E5 for issue [Phase E5] Undeliverable payee handling: deauthorised trustlines and rejecting contracts #55, and moved the changelog text into changelog.d/55-undeliverable-payee.md per changelog.d/README.md instead of appending to ROADMAP.md's ## Changelog section.

Error codes

Checked Error against main's current discriminants (0–34, through InvalidFundingDeadline = 34 from the pooled-escrow work). Undeliverable = 35 is still the next free, append-only slot — no collision, nothing else claimed 35 on main since this PR branched. Left it as-is.

Snapshots

Ran cargo test in contracts/escrow on top of the merge. Two committed snapshots changed because release's payout now goes through try_transfer:

  • unauthorized_sac_payee_leaves_escrow_funded_and_can_be_retried.1.json
  • rejecting_contract_payee_returns_dedicated_error_without_settling.1.json

Diff reviewed: both additions are a single extra DataKey::Status storage entry (Funded in the first snapshot after the rejected release, Released in the second after the retry succeeds) — that's main's status-read optimization (#260, a separate DataKey::Status entry alongside the escrow record), not a change in this PR's behavior. Committed as f6df6b7.

Also found the same kind of drift in the sibling contracts/releaser-account test snapshot (custom_account_can_be_configured_as_escrow_releaser.1.json) for the same reason — not covered by this PR's change, not enforced by the CI snapshot-drift job (which only checks contracts/escrow/test_snapshots), but stale now that it's merged with #260, so I regenerated and committed it too (61bc57c) rather than leave a known-stale snapshot behind.

Re-ran cargo test once more afterward; git status is clean, so "Fail if committed test snapshots changed" should now pass.

Bindings

./scripts/check-generated-bindings.sh with the pinned stellar 28.1.0 reports the committed sdk/src/generated/contract-client.ts already matches the contract WASM (it already carried error 35 from this PR's earlier work) — no regeneration needed.

Other checks run locally

  • cargo test (contracts/escrow): 137 passed, 0 failed.
  • cargo test --test model (PROPTEST_CASES=64): 4 passed.
  • cargo build --target wasm32v1-none --release: wasm artifact produced.
  • cargo test/cargo build --target wasm32v1-none --release for contracts/releaser-account: green.
  • python3 scripts/check-resource-baseline.py: OK (13 profiled functions).
  • cargo fmt --check: fails, but only on pre-existing formatting drift in files this PR doesn't touch (benches/resource_profile.rs, tests/cancel.rs, tests/malicious_token.rs, tests/model.rs, tests/pooled.rs, and a few spots in src/lib.rs outside this PR's lines) — confirmed pre-existing on main before merging, left alone rather than reformatting unrelated code in this PR.
  • cargo clippy --all-targets: warnings only (no CI job actually runs clippy), and all of them are pre-existing — too_many_arguments on the existing create_escrow* entry points, deprecated events().publish, a few needless_borrow/clone_on_copy/dead-code lints in test files this PR doesn't touch. Nothing in tests/undeliverable.rs or the new settle() branch.
  • SDK (sdk/): npm ci --ignore-scripts, then npm run typecheck, npm run build, npm run test:offline (64 passed), test:milestones (19 passed), test:unit (56 passed), test:runtime — all green. Didn't run the live-testnet suites (npm test / test:integration), consistent with what CI itself skips.

Diff review

Full diff vs origin/main is 10 files / ~1500 lines, almost all of it the two regenerated test-snapshot JSON files. No unrelated files, no secrets, no generated-file hand-edits, nothing that looks obfuscated.

Final branch head: 61bc57c.

@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 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 26, 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 preserved the undeliverable-payee retry behavior. 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 PR uses version 2.3.0; #155 is based on it and uses 2.4.0, so please merge #154 before #155 to keep the version sequence.

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

@afeezorobsco-cyber afeezorobsco-cyber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweep: malware scan clean, CI green.

@afeezorobsco-cyber
afeezorobsco-cyber merged commit 1df1206 into DocForum:main Oct 28, 2026
13 checks passed
afeezorobsco-cyber added a commit that referenced this pull request Oct 28, 2026
…to a new address (#199)

* feat(contract): payee rights transfer: payee reassigns future payout to a new address

Closes #45

- Add transfer_payee(escrow_id, current_payee, new_payee) restricted to
  the current payee (require_auth), only while Funded.
- Reject contract address and payer as new_payee (InvalidNewPayee = 8).
- Emit pay_xfer event with (escrow_id, old_payee, new_payee).
- 12 tests in contracts/escrow/tests/transfer_payee.rs covering:
  happy path, release/refund after transfer, chained transfers,
  auth rejection (stranger/payer/releaser), both rejected targets,
  and state checks (after release, after refund, unknown id).
- Threat model: I1 updated to document transfer_payee scope, payee
  actor updated (new on-chain power), T11 added (payee key surface).
- ROADMAP.md Phase E5 item added, changelog entry dated 2026-10-19.
- ARCHITECTURE_ESSENTIALS.md and README.md contract surface updated.

Design note: this is a rotation tool. A payee who has lost their key
cannot use it — they cannot sign current_payee.require_auth(). Key
loss remains an off-chain operational risk. Documented in T11.

* fix(ci): repair transfer_payee integration after merging main

Three CI jobs failed on this branch because the feature was written
against a pre-merge main and never re-integrated:

- Regenerate sdk/src/generated/contract-client.ts from the merged
  contract WASM with the pinned stellar CLI 28.1.0: the contract
  gained transfer_payee and error 35 (InvalidNewPayee), so the
  committed bindings no longer matched (issue #121 CI job).
- Update the create_escrow call in tests/transfer_payee.rs to the
  current signature (notify_payee + refund_to arguments).
- Commit the 12 Soroban test snapshots for the transfer_payee tests,
  which were never added (issue #120 snapshot-drift CI job).

Verified locally: cargo test (all suites incl. proptest model with
PROPTEST_CASES=64), snapshot drift clean, wasm build + artifact,
releaser-account tests and wasm, byte-identical rebuild via
check-wasm-reproducibility.sh, check-generated-bindings.sh OK,
resource baseline OK, and sdk typecheck/build/offline/milestones/
unit/runtime suites all pass.

* test(snapshots): refresh transfer_payee snapshots for main's cached Status entry

After merging main, cargo test writes the cached ["Status", id] ledger entry
(status-read optimisation) into the transfer_payee test snapshots, which were
generated before that change landed. All 12 files gain exactly one entry,
matching main's own snapshots.

* feat(contract): bump CONTRACT_VERSION to 1.1.0 for transfer_payee

CI (from #153) requires a CONTRACT_VERSION bump whenever lib.rs changes
against the base branch. transfer_payee is a new public function, so bump
the minor version and update the version_reports_the_contract_semver
assertion to match.

* fix(contract): keep failed releases recoverable (#154)

* fix(contract): keep failed releases recoverable

* fix(contract): reserve unique undeliverable error code

* fix(contract): reserve unique undeliverable error code

* test: enable revocation in undeliverable SAC fixture

* test(contract): refresh undeliverable payee snapshots

The status-read optimisation on main keeps a separate DataKey::Status
entry next to the escrow record. Regenerate the two undeliverable payee
snapshots so they include it: Funded after the rejected release, and
Released after the retry.

* docs(roadmap): record undeliverable payee handling (#55)

* test(releaser-account): refresh snapshot for status-read optimisation

Picked up from merging main's status-read change (#260): releaser-account's
own fixture now also has a separate DataKey::Status entry. Not touched by
this PR's change; regenerated by running its test suite.

---------

Co-authored-by: Ezekiel146 <320391431+Ezekiel146@users.noreply.github.com>
Co-authored-by: Pheelz <afeezorobsco@gmail.com>

* ci(contract): contract-change fragments instead of per-PR version bumps (#268)

Nine open PRs each bumping CONTRACT_VERSION meant every contract merge put
all the others into conflict, and each picked the same 'next' version.

- contract-changes.d/: a PR that changes lib.rs adds a fragment (bump level
  plus summary) and leaves CONTRACT_VERSION alone. New Error variants use a
  provisional code (issue x 100 + n) so two PRs can't pick the same number.
- scripts/check-contract-change.mjs replaces the 'Require a contract version
  bump' CI step: fragment required, no version edits, error codes append-only,
  new codes provisional. A fold PR (deletes fragments) may bump and renumber.
- scripts/fold-contract-changes.mjs: maintainer fold. Bumps the version once
  by the largest level, assigns final error codes, writes a changelog.d
  entry, deletes the fragments.
- version test compares with CONTRACT_VERSION instead of a literal.
- AGENTS.md and CONTRIBUTING.md document the rule.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* chore(changelog): fold changelog.d fragments into ROADMAP.md (#269)

Ran scripts/fold-changelog.mjs during the 2026-10-28 PR sweep.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(contract): payee rights transfer — transfer_payee reassigns the release destination to a new address

Only the current payee may authorise this; only while Funded; new_payee may
not be the contract address or the payer. Rotation tool, not key-loss
recovery. 12 tests in contracts/escrow/tests/transfer_payee.rs.

Adds contract-changes.d/45-payee-rights-transfer.md (bump: minor) instead of
editing CONTRACT_VERSION, per contract-changes.d/README.md, and gives
Error::InvalidNewPayee the provisional code 4500 (issue #45 x 100) for the
maintainer fold to renumber. Regenerates the SDK bindings from the rebuilt
WASM and adds changelog.d/45-payee-rights-transfer.md for issue #45.

Co-authored-by: Codebuff <noreply@codebuff.com>

---------

Co-authored-by: Ezekiel <ezekieldigitalz@gmail.com>
Co-authored-by: Ezekiel146 <320391431+Ezekiel146@users.noreply.github.com>
Co-authored-by: Pheelz <afeezorobsco@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
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.

[Phase E5] Undeliverable payee handling: deauthorised trustlines and rejecting contracts

3 participants