Skip to content

feat(payment-vault): User Booking Cancellation + Dynamic Precision Handling#53

Merged
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
KevinMB0220:feat/booking-cancellation-and-dynamic-precision
Mar 27, 2026
Merged

feat(payment-vault): User Booking Cancellation + Dynamic Precision Handling#53
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
KevinMB0220:feat/booking-cancellation-and-dynamic-precision

Conversation

@KevinMB0220
Copy link
Copy Markdown
Contributor

@KevinMB0220 KevinMB0220 commented Mar 27, 2026

Summary

Implements issues #36 and #38 for the payment-vault-contract.

Issue #36 — User Booking Cancellation

  • Added Cancelled = 5 to BookingStatus
  • Added started_at: Option<u64> to BookingRecord — set by Oracle when session becomes active
  • New mark_session_started(booking_id) function (Oracle-only): locks the booking against user cancellation
  • New cancel_booking(user, booking_id) function (User-only): refunds full deposit when booking is Pending and Oracle hasn't started it yet
  • New errors: SessionAlreadyStarted = 11, Overflow = 12
  • New events: booking_cancelled, session_started
  • New storage helper: update_booking_started_at

Issue #38 — Dynamic Precision Handling

  • Replaced * with checked_mul in book_session and finalize_session to prevent i128 overflow with high-precision tokens (e.g., 18-decimal ERC20 equivalents)
  • Added inline documentation explicitly stating that rate_per_second and total_deposit must be expressed in atomic units of the payment token
  • Returns VaultError::Overflow on multiplication overflow

Test plan

  • test_user_cancels_before_session_starts_success — user cancels immediately, full refund received, status is Cancelled
  • test_user_cannot_cancel_after_oracle_marks_started — Oracle marks session started, user cancel attempt fails, funds remain locked
  • test_booking_with_18_decimal_token_scale_no_overflow — rate of 10^18 atomic units/sec × 100 seconds = 10^20 deposit; no overflow, finalization and refund work correctly
  • All 41 existing tests continue to pass

Closes #36
Closes #38

Summary by CodeRabbit

  • New Features

    • Users can now cancel pending bookings before sessions begin and receive automatic deposit refunds.
    • Oracle agents can mark when sessions officially start.
  • Improvements

    • Enhanced payment calculation reliability with better overflow handling for large amounts.

…andling

Implements issues LightForgeHub#36 and LightForgeHub#38 for the payment-vault-contract.

Issue LightForgeHub#36 — User Booking Cancellation:
- Add `Cancelled = 5` variant to `BookingStatus`
- Add `started_at: Option<u64>` to `BookingRecord` to track when Oracle marks a session active
- Add `mark_session_started()` (Oracle-only) to set started_at on a booking
- Add `cancel_booking()` (User-only) — refunds full deposit when session is Pending and not yet started
- Add `SessionAlreadyStarted` and `Overflow` variants to `VaultError`
- Add `booking_cancelled` and `session_started` events
- Add `update_booking_started_at()` to storage layer
- Tests: cancel before start (success), cancel after Oracle marks started (fails)

Issue LightForgeHub#38 — Dynamic Precision Handling:
- Use `checked_mul` in `book_session` and `finalize_session` to prevent i128 overflow on high-precision tokens
- Document that `rate_per_second` must always be in atomic units of the payment token
- Test: simulate booking with 18-decimal scale (10^18 rate) to confirm no overflow

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

This PR extends the payment vault contract with user-initiated booking cancellation and oracle-driven session start tracking. It adds overflow protection to monetary calculations, introduces a new Cancelled booking status, and implements two new entrypoints: cancel_booking (user-authenticated) and mark_session_started (oracle-authenticated), along with supporting events and error variants.

Changes

Cohort / File(s) Summary
Core Contract Logic
contracts/payment-vault-contract/src/contract.rs
Implemented mark_session_started (oracle-authenticated) to transition bookings to started state with timestamp tracking, and cancel_booking (user-authenticated) to refund pending unbilled bookings. Added checked multiplication for overflow detection in deposit and payment calculations.
Type & Status Management
contracts/payment-vault-contract/src/types.rs
Added Cancelled = 5 variant to BookingStatus enum and started_at: Option<u64> field to BookingRecord struct to track oracle-marked session initiation timestamps.
Storage Operations
contracts/payment-vault-contract/src/storage.rs
Added update_booking_started_at helper function to persist oracle session start timestamps.
Error & Event Definitions
contracts/payment-vault-contract/src/error.rs, contracts/payment-vault-contract/src/events.rs
Extended VaultError with SessionAlreadyStarted and Overflow variants; added booking_cancelled and session_started event emitters.
Public Contract Interface
contracts/payment-vault-contract/src/lib.rs
Exposed mark_session_started and cancel_booking as public contract methods; updated documentation for set_my_rate and book_session to clarify atomic unit denomination.
Test Coverage
contracts/payment-vault-contract/src/test.rs
Added test cases for pre-start cancellation success, post-start cancellation rejection, and high-precision token (18 decimals) booking lifecycle without overflow.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Contract
    participant Storage
    participant TokenClient
    participant EventLog

    User->>Contract: cancel_booking(booking_id)
    Contract->>Storage: load booking
    Storage-->>Contract: BookingRecord{status: Pending, started_at: None}
    Contract->>TokenClient: transfer(user, total_deposit)
    TokenClient-->>Contract: ✓
    Contract->>Storage: update_booking_status(Cancelled)
    Contract->>EventLog: booking_cancelled(booking_id, amount)
    EventLog-->>Contract: ✓
    Contract-->>User: Ok(())
Loading
sequenceDiagram
    actor Oracle
    participant Contract
    participant Storage
    participant EventLog

    Oracle->>Contract: mark_session_started(booking_id)
    Contract->>Storage: load booking
    Storage-->>Contract: BookingRecord{status: Pending, started_at: None}
    Contract->>Storage: update_booking_started_at(current_timestamp)
    Storage-->>Contract: ✓
    Contract->>EventLog: session_started(booking_id, timestamp)
    EventLog-->>Contract: ✓
    Contract-->>Oracle: Ok(())
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hops merrily through the vault

Sessions can now start with a timestamp's call,
Refunds flow back ere the dance does fall,
No overflow shall breach our i128 wall,
Status Cancelled lets users stand tall! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the two main features implemented: user booking cancellation and dynamic precision handling with safe arithmetic.
Description check ✅ Passed The PR description covers Issue #36 and #38 tasks, explains changes across all modified files, includes test results, and follows the repository template structure with all major sections addressed.
Linked Issues check ✅ Passed All Issue #36 requirements (BookingStatus.Cancelled, BookingRecord.started_at, mark_session_started, cancel_booking, new errors/events) and Issue #38 requirements (checked_mul overflow prevention, atomic units documentation) are implemented and tested.
Out of Scope Changes check ✅ Passed All code changes (new types, functions, events, tests, documentation) directly support the two linked issues; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
contracts/payment-vault-contract/src/test.rs (1)

1331-1370: The new overflow handling still lacks a failing-case regression.

This validates a large valid amount, but it never forces the new checked_mul error path. A targeted test that exceeds i128 in book_session and finalize_session would lock in VaultError::Overflow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/payment-vault-contract/src/test.rs` around lines 1331 - 1370, The
test validates a large-but-valid deposit and never hits the new checked_mul
overflow path; add a failing-case regression that forces the overflow and
asserts VaultError::Overflow from book_session and/or finalize_session. Create a
new test (e.g., test_booking_overflow_triggers_vaulterror) that computes a
rate_per_second and/or duration so rate_per_second.checked_mul(duration as i128)
returns None (for example rate = i128::MAX / duration as i128 + 1 or use
duration large enough), mint any required tokens, call client.book_session (or
call finalize_session if overflow happens there), and assert the call returns
the VaultError::Overflow variant; reference the functions book_session,
finalize_session and the error VaultError::Overflow when locating where to
trigger and assert the overflow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/payment-vault-contract/src/contract.rs`:
- Around line 252-260: The booking is stamped with started_at but left in
BookingStatus::Pending, which allows reclaim_stale_session and reject_session to
operate on started sessions and permits overwriting started_at; update the code
in this path (around storage::get_booking, storage::update_booking_started_at,
events::session_started) to also transition the booking status out of Pending
(e.g., set to a Started/Active enum variant) via the appropriate storage update,
and emit any corresponding event, or alternatively modify reclaim_stale_session
and reject_session to first check booking.started_at.is_none() before acting;
pick one: either change this flow to call storage::update_booking_status(...) to
mark the booking started when stamping started_at, or add started_at.is_none()
guards in reclaim_stale_session/reject_session.

---

Nitpick comments:
In `@contracts/payment-vault-contract/src/test.rs`:
- Around line 1331-1370: The test validates a large-but-valid deposit and never
hits the new checked_mul overflow path; add a failing-case regression that
forces the overflow and asserts VaultError::Overflow from book_session and/or
finalize_session. Create a new test (e.g.,
test_booking_overflow_triggers_vaulterror) that computes a rate_per_second
and/or duration so rate_per_second.checked_mul(duration as i128) returns None
(for example rate = i128::MAX / duration as i128 + 1 or use duration large
enough), mint any required tokens, call client.book_session (or call
finalize_session if overflow happens there), and assert the call returns the
VaultError::Overflow variant; reference the functions book_session,
finalize_session and the error VaultError::Overflow when locating where to
trigger and assert the overflow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3bb98a4-1f40-4ff0-9711-724ad5ada36d

📥 Commits

Reviewing files that changed from the base of the PR and between c1cf02c and 41bb817.

📒 Files selected for processing (7)
  • contracts/payment-vault-contract/src/contract.rs
  • contracts/payment-vault-contract/src/error.rs
  • contracts/payment-vault-contract/src/events.rs
  • contracts/payment-vault-contract/src/lib.rs
  • contracts/payment-vault-contract/src/storage.rs
  • contracts/payment-vault-contract/src/test.rs
  • contracts/payment-vault-contract/src/types.rs

Comment on lines +252 to +260
let booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?;

if booking.status != BookingStatus::Pending {
return Err(VaultError::BookingNotPending);
}

let started_at = env.ledger().timestamp();
storage::update_booking_started_at(env, booking_id, started_at);
events::session_started(env, booking_id, started_at);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

A started booking still remains on the pre-start lifecycle path.

This only stamps started_at; it leaves the record Pending. reclaim_stale_session and reject_session still accept Pending bookings, so a started session can still be fully unwound later, and repeated calls here can overwrite the original start timestamp. Either move the booking out of Pending here or gate those paths on started_at.is_none().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/payment-vault-contract/src/contract.rs` around lines 252 - 260, The
booking is stamped with started_at but left in BookingStatus::Pending, which
allows reclaim_stale_session and reject_session to operate on started sessions
and permits overwriting started_at; update the code in this path (around
storage::get_booking, storage::update_booking_started_at,
events::session_started) to also transition the booking status out of Pending
(e.g., set to a Started/Active enum variant) via the appropriate storage update,
and emit any corresponding event, or alternatively modify reclaim_stale_session
and reject_session to first check booking.started_at.is_none() before acting;
pick one: either change this flow to call storage::update_booking_status(...) to
mark the booking started when stamping started_at, or add started_at.is_none()
guards in reclaim_stale_session/reject_session.

@Bosun-Josh121 Bosun-Josh121 merged commit 86d8467 into LightForgeHub:main Mar 27, 2026
2 checks 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.

Dynamic Precision Handling User Booking Cancellation

2 participants