Skip to content

feat(contracts): add milestone deadlines and time-locked escrow protections to escrow contract - #232

Open
devkylian-tech wants to merge 3 commits into
Lumina-eX:mainfrom
devkylian-tech:feat/221-milestone-deadlines
Open

devkylian-tech wants to merge 3 commits into
Lumina-eX:mainfrom
devkylian-tech:feat/221-milestone-deadlines

Conversation

@devkylian-tech

Copy link
Copy Markdown

Overview

Adds time-locked escrow protections to the Soroban escrow contract. Each milestone already carried a deadline field; this change completes the lifecycle around it — authorized deadline set/extend, on-chain expiry detection, client-initiated refunds after expiry, dispute lock-out, and guards that block submission/approval/release once a deadline has elapsed. New deadline events are published: DeadlineSet, DeadlineExtended, DeadlineExpired (alongside the existing RefundIssued).

Related Issue

#221 — [Feature]: Time-Locked Escrow & Milestone Deadlines

Changes

Escrow contract (contracts/contracts/escrow/src/lib.rs)

  • [ADD] set_deadline(milestone_id, caller, deadline)
    • Assigns a deadline to a milestone created without one. require_auth + role check restricts it to the client or arbiter; past deadlines are rejected (InvalidDeadline) and the call emits DeadlineSet.
  • [ADD] extend_deadline(milestone_id, caller, new_deadline)
    • Authorized extension (client or arbiter only) that must move strictly forward in time. Supports the Expired -> Extended transition and emits DeadlineExtended.
  • [ADD] claim_expired_refund(milestone_id, caller)
    • Client-initiated refund once env.ledger().timestamp() > milestone.deadline. Refunds Funded/Submitted milestones and emits DeadlineExpired + RefundIssued. Milestones with an open dispute stay locked (MilestoneDisputed) pending arbitration.
  • [MODIFY] approve and release
    • Both now return DeadlineExceeded when the milestone deadline has elapsed, so expired work can no longer be approved or paid out.
  • [MODIFY] auto_expire
    • Additionally emits DeadlineExpired next to the existing MilestoneExpired event.
  • [ADD] typed errors DeadlineNotExpired (13), InvalidDeadline (14), DeadlineAlreadySet (15), MilestoneDisputed (16), plus the DeadlineSet, DeadlineExtended, and DeadlineExpired events.

Tests (contracts/contracts/escrow/src/test.rs)

  • [ADD] 21 unit tests covering authorized/unauthorized set & extend, invalid/past/backwards deadlines, expiry guards on submit/approve/release, client refunds after expiry (funded-with-no-submission and submitted), disputes raised after expiry with funds locked then resolved by the arbiter, multiple milestones expiring at the same timestamp, and DeadlineExpired emission.
  • [ADD] generated contracts/contracts/escrow/test_snapshots/test/*.json files for the new tests.

Verification Results

Ran the repo's real Rust test command in a local checkout (REST tarball of main):

$ cd contracts && cargo test -p escrow

running 42 tests
...
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s

All 42 tests pass — the 21 pre-existing tests (unchanged behaviour, since milestones with deadline == 0 skip the new guards) plus the 21 new tests. No compiler warnings were produced.

Also ran the repo's real contract build (stellar contract build, stellar-cli 27.0.0), which produced an optimized wasm exposing the new entrypoints:

$ cd contracts && stellar contract build

✅ Build Complete
    Wasm File: target/wasm32v1-none/release/escrow.wasm (19367 bytes optimized)
    Wasm Hash: 8e41c01b40fd83bb065de24784ffecefc7b7c50bc7fd449253a52201db9a7b79
    Exported Functions: 26 found
      • approve
      • auto_expire
      • claim_expired_refund
      • extend_deadline
      • set_deadline
      ...
Acceptance Criteria Status
Contract rejects milestone approval/release after the deadline expired ✅ approve/release return DeadlineExceeded (#11); test_approve_after_deadline_expired_fails, test_release_after_deadline_expired_fails
Only client or arbitrator can modify deadlines ✅ set_deadline/extend_deadline require auth and check the caller role, else Unauthorized (#7)
Expired milestones handled gracefully (refund or locked) ✅ claim_expired_refund refunds Funded/Submitted; Disputed stays locked (#16)
Multiple milestones expiring simultaneously ✅ test_multiple_milestones_expiring_simultaneously
Disputes raised after expiry ✅ test_dispute_can_be_raised_after_expiry, test_claim_refund_locked_when_disputed_after_expiry, test_arbiter_resolves_dispute_raised_after_expiry
Escrow funded but no submission ✅ test_client_refund_after_expiry_when_funded_no_submission
Events DeadlineSet, DeadlineExpired, DeadlineExtended, RefundIssued ✅ emitted from set_deadline, auto_expire/claim_expired_refund, extend_deadline, and claim_expired_refund

Closes #221

@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@devkylian-tech 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

Resolves merge conflicts with Lumina-eX/TaskChain@main (base d623865) so the pull request can be merged.
Auto-merged via the GitHub API.
@devkylian-tech

Copy link
Copy Markdown
Author

Merge conflict with main resolved

Pushed merge commit 5e40909c6c into this branch (main @ d62386535d). This is a merge commit, not a force-push, so the commit history is intact.

Conflicting file(s) resolved:

  • contracts/contracts/escrow/test_snapshots/test/test_approve_after_deadline_expired_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_arbiter_resolves_dispute_raised_after_expiry.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_auto_expire_emits_deadline_expired.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_claim_refund_locked_when_disputed_after_expiry.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_client_refund_after_expiry_when_funded_no_submission.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_client_refund_after_expiry_when_submitted.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_client_refund_before_expiry_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_client_refund_unauthorized_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_dispute_can_be_raised_after_expiry.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_expired_milestone_can_be_extended_then_submitted.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_extend_deadline_backwards_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_extend_deadline_by_arbiter_succeeds.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_extend_deadline_unauthorized_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_extend_deadline_without_existing_deadline_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_multiple_milestones_expiring_simultaneously.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_release_after_deadline_expired_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_set_deadline_in_past_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_set_deadline_stores_value_and_emits_event.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_set_deadline_twice_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_set_deadline_unauthorized_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/test_snapshots/test/test_submit_after_deadline_expired_fails.1.json[hand-resolved]
  • contracts/contracts/escrow/src/lib.rs[hand-resolved]
  • contracts/contracts/escrow/src/test.rs[hand-resolved]

The pull request is mergeable again — CI will re-run on the new head. @SudiptaPaul-31 ready for review and merge when you have a moment.

Resolves merge conflicts with Lumina-eX/TaskChain@main (base d623865) so the pull request can be merged.
Auto-merged via the GitHub API.
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.

[Feature]: Time-Locked Escrow & Milestone Deadlines

1 participant