Skip to content

Conversation

@KevinMB0220
Copy link
Contributor

@KevinMB0220 KevinMB0220 commented Jan 23, 2026

🧠 SkillSphere Pull Request 🌐

Mark with an x all the checkboxes that apply (like [x])


📌 Type of Change

  • 📚 Documentation (updates to README, docs, or comments)
  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ Enhancement (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 🏗️ Refactor (code improvement/cleanup without logical changes)

📝 Changes Description

Implemented the Oracle Settlement (Finalize) feature for the Payment Vault Contract, enabling trustless, backend-verified session settlements with automatic payment distribution and refunds.

This feature allows a trusted Oracle (backend) to finalize consultation sessions by providing the actual session duration, ensuring correct expert payouts and user refunds in a fully on-chain, auditable manner.

🔑 Key Changes

1️⃣ Booking System (storage.rs)

  • Added BookingStatus enum (Pending, Complete) for lifecycle management
  • Introduced Booking struct with:
    • id, expert, user
    • rate, total_deposit
    • booked_duration
    • status
  • Implemented a booking counter for unique ID generation
  • Added storage helpers:
    • save_booking()
    • get_booking()
    • update_booking_status()

2️⃣ Core Contract Logic (contract.rs)

  • create_booking()

    • Allows users to create bookings with upfront token deposits
    • Validates rate and duration
    • Calculates total_deposit = rate × booked_duration
    • Transfers tokens from user to contract vault
    • Returns a unique booking ID
  • finalize_session() (Oracle-only)

    • Enforces Oracle authorization via oracle.require_auth() (critical security check)
    • Verifies booking existence and Pending status
    • Calculates:
      • expert_pay = rate × actual_duration
      • refund = total_deposit - expert_pay
    • Executes atomic token transfers (expert payment + user refund)
    • Updates booking status to Complete (prevents double finalization)
    • Emits SessionFinalized event

3️⃣ Error Handling (error.rs)

  • Added:
    • BookingNotFound
    • BookingNotPending
    • InvalidAmount

4️⃣ Events (events.rs)

  • Implemented SessionFinalized event for off-chain indexing and monitoring

5️⃣ Public API (lib.rs)

  • Exposed create_booking() and finalize_session() with inline documentation

🗄️ Storage Impact

  • Uses instance storage for BookingCounter
  • Uses persistent storage for Booking records
  • No breaking changes to existing storage layout

📸 Evidence

✅ Test Results (7 / 7 Passed)

Screenshot 2026-01-22 at 6 00 20 PM

🧪 Covered Scenarios

  • Partial duration settlement (refund issued)
  • Full duration usage (no refund)
  • Zero duration (full refund / cancellation)
  • Double-finalization protection
  • Oracle-only authorization enforcement
  • Booking not found handling
  • Vault initialization safety

🏗️ Build Status

  • Target: wasm32-unknown-unknown
  • Profile: release
  • Build successful ✅

🌌 Comments

🔐 Security Considerations

  • Oracle Authorization: finalize_session() enforces oracle.require_auth() to ensure only the trusted backend can trigger settlements.
  • Double-Finalization Protection: Booking status validation prevents re-processing completed bookings.
  • Arithmetic Safety: Explicit checks prevent negative or invalid payment calculations.

🔍 Review Focus Areas

  • Payment calculation logic
  • Oracle authorization enforcement
  • Conditional token transfers
  • Event payload correctness

🚀 Next Steps

  • Deploy to testnet for integration testing
  • Configure Oracle backend endpoint
  • Update frontend to support booking creation and status monitoring

Thank you for contributing to SkillSphere! 🌍
Together, we’re building a trustless, peer-to-peer consulting economy on Stellar. 🚀

Summary by CodeRabbit

  • New Features

    • Implemented booking creation workflow supporting user authorization, rate validation, and token deposit transfers
    • Added session finalization with payment computation, automatic refund processing, and booking lifecycle management
  • Tests

    • Expanded test coverage for booking scenarios including partial sessions and refund handling

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

The PR implements oracle-based session finalization and booking lifecycle management. Changes introduce token transfer logic for booking creation and settlement, new storage structures for booking state, error handling for validation failures, and event emission for finalized sessions.

Changes

Cohort / File(s) Summary
Core Contract Logic
contracts/payment-vault-contract/src/contract.rs
Implements create_booking to validate booking parameters, transfer total deposit from user to contract, and persist Booking state. Implements finalize_session with oracle authorization, expert payment computation, refund handling, and state transitions to Complete status.
Storage & State Management
contracts/payment-vault-contract/src/storage.rs
Introduces Booking struct and BookingStatus enum for state representation. Adds DataKey variants (Booking(u64), BookingCounter) for persistent storage. Provides helper functions for booking lifecycle: get_next_booking_id, save_booking, get_booking, update_booking_status.
Interface & Module Setup
contracts/payment-vault-contract/src/lib.rs
Exports new create_booking and finalize_session contract methods and adds events module import.
Error Handling
contracts/payment-vault-contract/src/error.rs
Adds three new enum variants to VaultError: BookingNotFound = 4, BookingNotPending = 5, InvalidAmount = 6 for distinct failure scenarios.
Event Emission
contracts/payment-vault-contract/src/events.rs
Introduces session_finalized function to emit structured event with booking ID, actual duration, and total cost.
Test Coverage
contracts/payment-vault-contract/src/test.rs
Expands test module with token contract helper and comprehensive booking scenarios: partial duration settlements, full duration without refund, double finalization protection, oracle authorization enforcement, and edge cases.

Sequence Diagrams

sequenceDiagram
    participant User
    participant Contract as Payment Vault
    participant Token as Token Contract

    User->>Contract: create_booking(user, expert, rate, duration)
    activate Contract
    Contract->>Contract: Validate rate & compute total_deposit
    Contract->>Token: transfer_from(user, contract, total_deposit)
    activate Token
    Token-->>Contract: ✓ transfer confirmed
    deactivate Token
    Contract->>Contract: Create & persist Booking (Pending)
    Contract->>Contract: Increment & store BookingCounter
    Contract-->>User: booking_id
    deactivate Contract
Loading
sequenceDiagram
    participant Oracle
    participant Contract as Payment Vault
    participant Token as Token Contract
    participant Expert
    participant User

    Oracle->>Contract: finalize_session(booking_id, actual_duration)
    activate Contract
    Contract->>Contract: oracle.require_auth()
    Contract->>Contract: Load & validate Booking (Pending)
    Contract->>Contract: Compute expert_pay & refund
    Contract->>Token: transfer(expert, expert_pay)
    activate Token
    Token->>Expert: deposit expert_pay
    Token-->>Contract: ✓ transfer confirmed
    deactivate Token
    alt refund > 0
        Contract->>Token: transfer(user, refund)
        activate Token
        Token->>User: deposit refund
        Token-->>Contract: ✓ transfer confirmed
        deactivate Token
    end
    Contract->>Contract: Update Booking status to Complete
    Contract->>Contract: Emit SessionFinalized event
    Contract-->>Oracle: ✓ success
    deactivate Contract
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Implement "Book Session" (Deposit) #6: Implements booking creation flow with Booking struct, storage helpers, and token transfers from user to contract, directly supporting the booking initialization objective.

Poem

🐰 A vault so grand, with bookings sealed,
Oracle speaks, and refunds are revealed,
Token streams flow to expert and user true,
Settlement dance in blockchain's hue! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Title mentions 'events' but the primary feature is Oracle-driven session finalization with booking system, not just events. Consider a more descriptive title like 'Feat: Oracle settlement and booking system' to better reflect the main functionality.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and well-structured, covering all required sections including issue closure, type of change, detailed changes, test evidence, and security considerations.
Linked Issues check ✅ Passed All requirements from issue #7 are met: Oracle authorization enforced, booking validation, correct payment calculations, atomic transfers, status updates, event emission, API exposure, and tests covering scenarios including double-finalization protection.
Out of Scope Changes check ✅ Passed All changes directly support the Oracle settlement feature: booking storage system, contract logic, error handling, event emission, API exposure, and comprehensive tests are all in scope.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

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

🤖 Fix all issues with AI agents
In `@contracts/payment-vault-contract/src/contract.rs`:
- Around line 91-98: Add an explicit validation before computing
expert_pay/refund: check if actual_duration > booking.booked_duration and either
(a) return a clearer error variant (e.g.,
VaultError::ActualDurationExceedsBooked) instead of InvalidAmount, or (b) cap
actual_duration = booking.booked_duration (use std::cmp::min) before calculating
expert_pay and refund; ensure this check is placed prior to using
actual_duration in the expert_pay and refund calculations and update any
affected error handling or tests accordingly.

In `@contracts/payment-vault-contract/src/storage.rs`:
- Around line 64-73: get_next_booking_id currently reads and writes
DataKey::BookingCounter from instance storage (env.storage().instance()), which
can expire and lead to booking ID reuse; change it to use persistent storage
(env.storage().persistent()) when getting and setting BookingCounter so the
counter survives instance TTL and prevents ID collisions with persistent Booking
records. Locate get_next_booking_id and replace the instance().get/set calls for
DataKey::BookingCounter with persistent().get/set while preserving the logic
that increments and returns next. Ensure you reference DataKey::BookingCounter,
get_next_booking_id, and the existing Booking persistent storage semantics when
making the change.
🧹 Nitpick comments (5)
contracts/payment-vault-contract/src/storage.rs (1)

88-93: Silent no-op if booking doesn't exist.

update_booking_status silently does nothing when the booking is not found. While the current caller (finalize_session) validates existence beforehand, this could mask bugs in future usage.

Consider returning a bool or Option to indicate whether the update succeeded.

♻️ Optional improvement
-pub fn update_booking_status(env: &Env, booking_id: u64, status: BookingStatus) {
-    if let Some(mut booking) = get_booking(env, booking_id) {
+pub fn update_booking_status(env: &Env, booking_id: u64, status: BookingStatus) -> bool {
+    if let Some(mut booking) = get_booking(env, booking_id) {
         booking.status = status;
         save_booking(env, &booking);
+        true
+    } else {
+        false
     }
 }
contracts/payment-vault-contract/src/contract.rs (2)

40-45: Potential arithmetic overflow in deposit calculation.

The multiplication rate * (booked_duration as i128) on line 41 can overflow for large values. While i128 has a large range, extremely high rates or durations could cause unexpected behavior.

The subsequent total_deposit <= 0 check partially guards against this (signed overflow wraps), but explicit overflow handling would be clearer.

♻️ Suggested safer calculation
     // Calculate total deposit
-    let total_deposit = rate * (booked_duration as i128);
-
-    if total_deposit <= 0 {
-        return Err(VaultError::InvalidAmount);
-    }
+    let total_deposit = rate.checked_mul(booked_duration as i128)
+        .filter(|&d| d > 0)
+        .ok_or(VaultError::InvalidAmount)?;

92-93: Same arithmetic overflow concern in finalization.

expert_pay = booking.rate * (actual_duration as i128) has the same overflow risk. Consider using checked_mul for consistency with the suggested improvement in create_booking.

♻️ Safer calculation
     // 4. Calculate payments
-    let expert_pay = booking.rate * (actual_duration as i128);
-    let refund = booking.total_deposit - expert_pay;
-
-    // Ensure calculations are valid
-    if expert_pay < 0 || refund < 0 {
-        return Err(VaultError::InvalidAmount);
-    }
+    let expert_pay = booking.rate
+        .checked_mul(actual_duration as i128)
+        .ok_or(VaultError::InvalidAmount)?;
+    let refund = booking.total_deposit
+        .checked_sub(expert_pay)
+        .filter(|&r| r >= 0)
+        .ok_or(VaultError::InvalidAmount)?;
contracts/payment-vault-contract/src/test.rs (2)

8-8: Unused extern crate std import.

This line appears unused in the current tests. If it was added for debugging (e.g., println!), consider removing it before merging.

♻️ Remove if unused
-extern crate std;

207-221: Consider adding tests for additional edge cases.

The test suite covers core scenarios well. Consider adding tests for:

  1. create_booking with rate <= 0 (should return InvalidAmount)
  2. create_booking with booked_duration = 0 (should return InvalidAmount)
  3. finalize_session with actual_duration > booked_duration (currently returns InvalidAmount)

These would strengthen validation coverage.

💡 Example test for invalid rate
#[test]
fn test_invalid_rate_rejected() {
    let env = Env::default();
    env.mock_all_auths();

    let admin = Address::generate(&env);
    let user = Address::generate(&env);
    let expert = Address::generate(&env);
    let oracle = Address::generate(&env);

    let token_admin = Address::generate(&env);
    let token = create_token_contract(&env, &token_admin);
    token.mint(&user, &10_000);

    let client = create_client(&env);
    client.init(&admin, &token.address, &oracle);

    // Rate of 0 should fail
    let result = client.try_create_booking(&user, &expert, &0, &100);
    assert!(result.is_err());
}

Comment on lines +91 to +98
// 4. Calculate payments
let expert_pay = booking.rate * (actual_duration as i128);
let refund = booking.total_deposit - expert_pay;

// Ensure calculations are valid
if expert_pay < 0 || refund < 0 {
return Err(VaultError::InvalidAmount);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation: actual_duration can exceed booked_duration.

The Oracle can pass an actual_duration greater than booked_duration, which would make refund negative (line 93) and trigger InvalidAmount. While this prevents overpayment, the error is misleading.

Consider adding explicit validation to reject actual_duration > booked_duration with a clearer error, or cap actual_duration at booked_duration.

🔧 Option 1: Explicit validation
+    // Ensure actual duration doesn't exceed booked duration
+    if actual_duration > booking.booked_duration {
+        return Err(VaultError::InvalidAmount);
+    }
+
     // 4. Calculate payments
     let expert_pay = booking.rate * (actual_duration as i128);
     let refund = booking.total_deposit - expert_pay;
-
-    // Ensure calculations are valid
-    if expert_pay < 0 || refund < 0 {
-        return Err(VaultError::InvalidAmount);
-    }
🔧 Option 2: Cap duration
+    let capped_duration = actual_duration.min(booking.booked_duration);
+
     // 4. Calculate payments
-    let expert_pay = booking.rate * (actual_duration as i128);
+    let expert_pay = booking.rate * (capped_duration as i128);
     let refund = booking.total_deposit - expert_pay;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 4. Calculate payments
let expert_pay = booking.rate * (actual_duration as i128);
let refund = booking.total_deposit - expert_pay;
// Ensure calculations are valid
if expert_pay < 0 || refund < 0 {
return Err(VaultError::InvalidAmount);
}
// 4. Calculate payments
let capped_duration = actual_duration.min(booking.booked_duration);
let expert_pay = booking.rate * (capped_duration as i128);
let refund = booking.total_deposit - expert_pay;
// Ensure calculations are valid
if expert_pay < 0 || refund < 0 {
return Err(VaultError::InvalidAmount);
}
🤖 Prompt for AI Agents
In `@contracts/payment-vault-contract/src/contract.rs` around lines 91 - 98, Add
an explicit validation before computing expert_pay/refund: check if
actual_duration > booking.booked_duration and either (a) return a clearer error
variant (e.g., VaultError::ActualDurationExceedsBooked) instead of
InvalidAmount, or (b) cap actual_duration = booking.booked_duration (use
std::cmp::min) before calculating expert_pay and refund; ensure this check is
placed prior to using actual_duration in the expert_pay and refund calculations
and update any affected error handling or tests accordingly.

Comment on lines +64 to +73
pub fn get_next_booking_id(env: &Env) -> u64 {
let current: u64 = env
.storage()
.instance()
.get(&DataKey::BookingCounter)
.unwrap_or(0);
let next = current + 1;
env.storage().instance().set(&DataKey::BookingCounter, &next);
next
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Storage type mismatch could cause booking ID reuse.

BookingCounter uses instance storage (which has TTL and can expire), while Booking records use persistent storage. If the instance storage expires and is later recreated, the counter resets to 0, potentially causing ID collisions with existing persistent bookings.

Consider using persistent storage for BookingCounter to ensure IDs remain unique across the contract's lifetime.

🔧 Suggested fix
 pub fn get_next_booking_id(env: &Env) -> u64 {
     let current: u64 = env
         .storage()
-        .instance()
+        .persistent()
         .get(&DataKey::BookingCounter)
         .unwrap_or(0);
     let next = current + 1;
-    env.storage().instance().set(&DataKey::BookingCounter, &next);
+    env.storage().persistent().set(&DataKey::BookingCounter, &next);
     next
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn get_next_booking_id(env: &Env) -> u64 {
let current: u64 = env
.storage()
.instance()
.get(&DataKey::BookingCounter)
.unwrap_or(0);
let next = current + 1;
env.storage().instance().set(&DataKey::BookingCounter, &next);
next
}
pub fn get_next_booking_id(env: &Env) -> u64 {
let current: u64 = env
.storage()
.persistent()
.get(&DataKey::BookingCounter)
.unwrap_or(0);
let next = current + 1;
env.storage().persistent().set(&DataKey::BookingCounter, &next);
next
}
🤖 Prompt for AI Agents
In `@contracts/payment-vault-contract/src/storage.rs` around lines 64 - 73,
get_next_booking_id currently reads and writes DataKey::BookingCounter from
instance storage (env.storage().instance()), which can expire and lead to
booking ID reuse; change it to use persistent storage
(env.storage().persistent()) when getting and setting BookingCounter so the
counter survives instance TTL and prevents ID collisions with persistent Booking
records. Locate get_next_booking_id and replace the instance().get/set calls for
DataKey::BookingCounter with persistent().get/set while preserving the logic
that increments and returns next. Ensure you reference DataKey::BookingCounter,
get_next_booking_id, and the existing Booking persistent storage semantics when
making the change.

@Bosun-Josh121 Bosun-Josh121 merged commit b26972c into LightForgeHub:main Jan 23, 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.

Implement "Oracle Settlement" (Finalize)

2 participants