Skip to content

Wilfred - #30

Merged
meshackyaro merged 2 commits into
workman-labs:developmentfrom
bytebenders1:wilfred
Jul 23, 2026
Merged

Wilfred#30
meshackyaro merged 2 commits into
workman-labs:developmentfrom
bytebenders1:wilfred

Conversation

@Wilfred007

Copy link
Copy Markdown
Contributor

Closes #14

Summary

Extend the escrow contract to support milestone-based, time-locked partial fund releases with pluggable multi-party arbitration hooks and reentrancy-safe settlement. This enables clients and workers to define granular milestones with individual deadlines and amounts, releasing funds incrementally as work progresses rather than in a single lump sum.

Changes

  • Add MilestoneStatus, Milestone, ArbitrationMode, MilestoneEscrow, and MilestoneEscrowInit types to soroban-contracts/contracts/escrow/src/lib.rs
  • Add 12 new error variants (19–30) for milestone-specific validation: MilestoneNotFound, InvalidMilestoneAmount, MilestoneAlreadyApproved, MilestoneTimeLocked, MilestoneAlreadyReleased, InvalidEscrowStatus, ArbitrationFailed, NotAClient, NotAWorker, MilestoneAmountMismatch, InvalidMilestoneCount, InvalidDeadline
  • Implement create_milestone_escrow — funds escrow with client deposit, supports AdminArbiter or ExternalHook arbitration modes
  • Implement add_milestone — client adds milestones with description hash, amount, and time-lock deadline; validates sum equals total
  • Implement approve_milestone — client approves a milestone, marking it ready for time-locked release
  • Implement release_milestone_funds — permissionless release after approval + deadline expiry; follows checks-effects-interactions for reentrancy safety
  • Implement raise_milestone_dispute — either party disputes a pending/approved milestone, freezing the escrow
  • Implement resolve_milestone_dispute — arbiter or external hook resolves dispute, routing funds to client or worker
  • Add get_milestone_escrow and get_milestone view functions
  • Add storage layout table and authorization model documentation to module doc
  • Add DataKey::MilestoneEscrow(u64) for persistent milestone escrow storage (coexists with existing DataKey::Appointment)
  • Bundle create_milestone_escrow parameters into MilestoneEscrowInit struct to satisfy clippy's 7-argument limit

Testing

Command: cargo test --workspace — 160 tests passing across 6 crates

Escrow crate: 37 tests (11 existing + 26 new)

New milestone escrow tests:

  • Happy paths: milestone_escrow_create_funds_contract, milestone_escrow_add_approve_release_happy_path, milestone_escrow_admin_arbitration_to_worker, milestone_escrow_admin_arbitration_to_client, partial_release_tracks_accounting_correctly, resolve_dispute_funds_returned_to_contract_balance, get_milestone_view_returns_correct_data
  • Failure cases: milestone_escrow_zero_amount_rejected, milestone_escrow_negative_amount_rejected, milestone_escrow_duplicate_id_rejected, add_milestone_non_client_rejected, add_milestone_exceeds_total_rejected, add_milestone_past_deadline_rejected, add_milestone_zero_amount_rejected, approve_milestone_already_approved_rejected, approve_milestone_not_found_rejected, release_before_deadline_rejected, release_not_approved_rejected, release_not_found_rejected
  • Adversarial edge cases: dispute_after_release_fails, dispute_by_non_participant_rejected, resolve_dispute_on_non_disputed_milestone_rejected, resolve_dispute_on_already_released_milestone_rejected, release_twice_same_milestone_rejected, dispute_freezes_further_releases, get_milestone_not_found_returns_error

CI checks:

  • cargo fmt --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo build --workspace --release --target wasm32v1-none — optimized WASM builds successfully

Tradeoffs

  • Single-level milestone disputes: A dispute on any milestone freezes the entire escrow (not just the disputed milestone). This is simpler and safer than per-milestone isolation, which would require tracking individual fund pools and create cross-milestone accounting complexity.
  • ExternalHook authorization: The external hook contract is expected to implement its own authorization logic. The escrow contract verifies the hook address exists but doesn't enforce a specific interface — this keeps the hook pluggable but shifts security responsibility to the hook implementer.
  • Milestone amounts must sum to total: The contract enforces sum(milestone.amounts) <= total_amount rather than requiring strict equality, allowing clients to leave a reserve. The remaining funds stay locked until the escrow is completed or resolved.
  • Coexistence with simple escrow: The new milestone escrow uses DataKey::MilestoneEscrow(id) which is namespace-separate from the existing DataKey::Appointment(id), so both systems coexist without affecting each other.

Architecture

Storage layout:

Key Durability Type Holds
DataKey::Admin instance Address Admin/arbiter
DataKey::Appointment(id) persistent Appointment Simple escrow state
DataKey::MilestoneEscrow(id) persistent MilestoneEscrow Milestone escrow state
GovernanceDataKey::* instance governance-guard types M-of-N upgrade governance

Authorization model:

  • Client auth required for: create, add_milestone, approve_milestone, confirm_completion, cancel
  • Participant auth required for: raise_dispute, raise_milestone_dispute
  • Arbiter auth required for: resolve_dispute, resolve_milestone_dispute (AdminArbiter mode)
  • Permissionless: release_milestone_funds (once conditions are met)

Reentrancy prevention: All state-mutating functions follow checks-effects-interactions — storage is updated before any token::Client::transfer call.

Out of Scope

  • Signer rotation for governance (left as deliberate follow-up per governance-guard design)
  • External hook contract implementation (this PR provides the hook address plumbing; the hook contract itself is a separate deliverable)
  • Milestone description storage on-chain (only the 32-byte hash is stored; full descriptions live off-chain)
  • Escrow cancellation / refund for milestone escrows (only dispute resolution routes funds back)
  • Dynamic milestone modification after creation (milestones are immutable once added)

“Wilfred007” added 2 commits July 22, 2026 10:00
…truct

Add Milestone, MilestoneEscrow, MilestoneEscrowInit, ArbitrationMode,
and MilestoneStatus types to the escrow contract. Introduce 12 new error
variants (19-30) for milestone-specific validation. Bundle
create_milestone_escrow parameters into MilestoneEscrowInit to satisfy
clippy's argument-count lint. Include storage layout and authorization
model documentation.
Add 26 new tests covering happy paths, failure cases, and adversarial
edge cases for the milestone escrow feature. Tests exercise: creation,
milestone lifecycle, time-locked release, admin arbitration, partial
release accounting, dispute handling, and double-spend prevention.
Add PartialEq derive to Milestone for test assertions.

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

Good job @Wilfred007. Thanks for contributing to Guildworkman

@meshackyaro
meshackyaro merged commit 5350321 into workman-labs:development Jul 23, 2026
1 check passed
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.

Time-Locked Milestone Escrow with Partial Release & Arbitration Hooks

2 participants