Skip to content

feat: add expert rate support to payment vault#29

Merged
Bosun-Josh121 merged 2 commits intoLightForgeHub:mainfrom
dinahmaccodes:issue-23
Feb 25, 2026
Merged

feat: add expert rate support to payment vault#29
Bosun-Josh121 merged 2 commits intoLightForgeHub:mainfrom
dinahmaccodes:issue-23

Conversation

@dinahmaccodes
Copy link
Contributor

@dinahmaccodes dinahmaccodes commented Feb 25, 2026

Closes #23

Description

This PR addresses Issue #23 by implementing an automated, secure expert rate configuration in the payment-vault-contract.

It introduces a proper rate storage mechanism that ensures experts have full control over their own pricing. The book_session function has been updated to verify and calculate deposits entirely based on this trusted on-chain stored rate, eliminating the risk of users providing arbitrary rates.

Finally, a suite of tests has been provided to guarantee deposit calculation accuracy and authorization consistency.

When this command is run, test passes for the changes I made

cd contracts/payment-vault-contract
cargo test
image

Summary by CodeRabbit

  • New Features

    • Experts can set and update their per-second session rates.
    • Session booking now uses the expert's stored rate.
    • Added session finalization and reclamation actions.
    • Emits an event when an expert updates their rate.
  • Bug Fixes

    • Bookings now fail when an expert's rate is not configured; rate validation added.
  • Tests

    • Expanded tests covering rate setting, deposit calculation, and booking failure when rate is unset.

@dinahmaccodes
Copy link
Contributor Author

@Bosun-Josh121
Please review PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Warning

Rate limit exceeded

@dinahmaccodes has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 567d5e4 and 8fbdaad.

📒 Files selected for processing (1)
  • contracts/payment-vault-contract/src/test.rs
📝 Walkthrough

Walkthrough

Introduces on-chain expert pricing: experts can call set_my_rate to store a per-second rate; book_session no longer accepts a rate and instead loads the expert's rate from storage, computes deposit, and fails if the expert rate is not set. Supporting error, event, storage, lib, and tests updated.

Changes

Cohort / File(s) Summary
Storage Layer
contracts/payment-vault-contract/src/storage.rs
Added DataKey::ExpertRate(Address), set_expert_rate(env, expert, rate) and get_expert_rate(env, expert) -> Option<i128> to persist and read expert rates.
Contract Logic
contracts/payment-vault-contract/src/contract.rs
Added pub fn set_my_rate(env, expert, rate_per_second) with auth, validation (>0), storage write and event emission. Updated book_session signature to remove rate_per_second and now loads rate from storage, returning ExpertRateNotSet if missing.
Public API (lib)
contracts/payment-vault-contract/src/lib.rs
Added public set_my_rate(env, expert, rate_per_second). Updated book_session signature to match contract change. Added delegating public methods finalize_session and reclaim_stale_session.
Errors
contracts/payment-vault-contract/src/error.rs
Added VaultError::ExpertRateNotSet = 8 to signal missing expert rate on booking.
Events
contracts/payment-vault-contract/src/events.rs
Added expert_rate_updated(env, expert, rate) event emitter (topics: symbol_short!("rate_upd"), expert) with payload rate.
Tests
contracts/payment-vault-contract/src/test.rs
Updated tests to call set_my_rate before booking. Added tests: test_expert_can_set_and_update_rate, test_book_session_calculates_correct_deposit, and test_book_session_fails_if_expert_rate_not_set. Adjusted booking_id handling across tests.

Sequence Diagram(s)

sequenceDiagram
    participant Expert as Expert
    participant User as User
    participant Contract as Payment Vault Contract
    participant Storage as Storage
    participant Ledger as Ledger/Token

    Expert->>Contract: set_my_rate(rate_per_second)
    Contract->>Contract: require_auth & validate rate > 0
    Contract->>Storage: set_expert_rate(expert, rate_per_second)
    Storage-->>Contract: ack
    Contract->>Contract: emit ExpertRateUpdated event
    Contract-->>Expert: success

    User->>Contract: book_session(user, expert, max_duration)
    Contract->>Storage: get_expert_rate(expert)
    Storage-->>Contract: rate_per_second (or none)
    alt rate set
      Contract->>Contract: compute total_deposit = rate * max_duration
      Contract->>Ledger: transfer_from(user -> contract, total_deposit)
      Ledger-->>Contract: transfer ack
      Contract->>Contract: create booking record & emit BookingCreated
      Contract-->>User: booking_id
    else rate missing
      Contract-->>User: error ExpertRateNotSet
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐇 I hopped to the ledger, nose in the code,
set my rate on-chain down a tidy road.
No sly user tricks, no secret debate—
bookings now trust the expert's set rate. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately and concisely describes the main change: adding expert rate support to the payment vault contract.
Description check ✅ Passed The PR description is mostly complete, addressing the issue number, summarizing the changes, and providing test evidence; however, it lacks some template sections like explicit type of change checkbox.
Linked Issues check ✅ Passed All coding requirements from issue #23 are met: storage layer adds ExpertRate key and helper functions, set_my_rate function validates and stores rates with auth, book_session signature updated and fetches stored rate, and comprehensive tests validate functionality.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #23 requirements; no out-of-scope modifications detected beyond the expert rate support feature.
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
  • Post copyable unit tests in a comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
contracts/payment-vault-contract/src/contract.rs (1)

58-58: ⚠️ Potential issue | 🔴 Critical

Use checked arithmetic to avoid runtime traps.

Line [58], Line [116], Line [117], and Line [171] currently use unchecked math. Large inputs can overflow and panic, causing transaction failure instead of a controlled VaultError.

🛠️ Proposed fix (checked math + explicit error path)
@@
-    let total_deposit = rate_per_second * (max_duration as i128);
+    let total_deposit = rate_per_second
+        .checked_mul(max_duration as i128)
+        .ok_or(VaultError::InvalidAmount)?;
@@
-    let expert_pay = booking.rate_per_second * (actual_duration as i128);
-    let refund = booking.total_deposit - expert_pay;
+    let expert_pay = booking
+        .rate_per_second
+        .checked_mul(actual_duration as i128)
+        .ok_or(VaultError::InvalidAmount)?;
+    let refund = booking
+        .total_deposit
+        .checked_sub(expert_pay)
+        .ok_or(VaultError::InvalidAmount)?;
@@
-    if current_time <= booking.created_at + RECLAIM_TIMEOUT {
+    let reclaim_after = booking
+        .created_at
+        .checked_add(RECLAIM_TIMEOUT)
+        .ok_or(VaultError::InvalidAmount)?;
+    if current_time <= reclaim_after {
         return Err(VaultError::ReclaimTooEarly);
     }

Also applies to: 116-117, 171-171

🤖 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` at line 58, Replace the
unchecked multiplications with checked arithmetic and return a controlled
VaultError on overflow: where you compute total_deposit using rate_per_second
and max_duration (and the other occurrences at the sites using the same
pattern), use checked_mul/checked_add/checked_sub as appropriate (e.g.,
rate_per_second.checked_mul(max_duration as i128)) and map None to
Err(VaultError::ArithmeticOverflow) (or the existing numeric error variant)
instead of letting the operation panic; update the expressions that set
total_deposit and the calculations at the other referenced sites (lines using
rate_per_second, max_duration, and any similar multiplications) to follow this
pattern so all overflow paths return a VaultError.
🤖 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/test.rs`:
- Around line 61-64: Tests bind the result to _booking_id but later assertions
and calls use booking_id, causing undefined identifier errors; update each block
that currently declares _booking_id (e.g., the block calling
client.set_my_rate(&expert, &rate_per_second); client.book_session(&user,
&expert, &max_duration)) to bind to booking_id instead, and apply the same
rename in all other similar blocks (those calling client.book_session /
client.set_my_rate sequences) so downstream uses of booking_id resolve
correctly.

---

Outside diff comments:
In `@contracts/payment-vault-contract/src/contract.rs`:
- Line 58: Replace the unchecked multiplications with checked arithmetic and
return a controlled VaultError on overflow: where you compute total_deposit
using rate_per_second and max_duration (and the other occurrences at the sites
using the same pattern), use checked_mul/checked_add/checked_sub as appropriate
(e.g., rate_per_second.checked_mul(max_duration as i128)) and map None to
Err(VaultError::ArithmeticOverflow) (or the existing numeric error variant)
instead of letting the operation panic; update the expressions that set
total_deposit and the calculations at the other referenced sites (lines using
rate_per_second, max_duration, and any similar multiplications) to follow this
pattern so all overflow paths return a VaultError.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3480c49 and 063eb26.

📒 Files selected for processing (6)
  • 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

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

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

273-276: Consider extracting a helper for repeated “set rate + book” setup.

This pattern is repeated many times; a small helper will reduce drift and make test intent clearer.

♻️ Suggested refactor
+fn set_rate_and_book(
+    client: &PaymentVaultContractClient<'_>,
+    user: &Address,
+    expert: &Address,
+    rate_per_second: i128,
+    max_duration: u64,
+) -> u32 {
+    client.set_my_rate(expert, &rate_per_second);
+    client.book_session(user, expert, &max_duration)
+}
-    let booking_id = {
-        client.set_my_rate(&expert, &rate_per_second);
-        client.book_session(&user, &expert, &max_duration)
-    };
+    let booking_id = set_rate_and_book(&client, &user, &expert, rate_per_second, max_duration);
🤖 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 273 - 276, Extract
the repeated "set rate + book" sequence into a small helper function (e.g., fn
set_rate_and_book(client: &mut Client, user: &User, expert: &Expert, rate:
&Rate, max_duration: &Duration) -> BookingId) that calls
client.set_my_rate(&expert, &rate_per_second) then client.book_session(&user,
&expert, &max_duration) and returns the booking id; replace the inline block
that assigns booking_id with a call to set_rate_and_book to reduce duplication
across tests and make intent clearer (update all occurrences of
client.set_my_rate and client.book_session to use this new helper).

700-724: Rate update test does not yet prove updated value is actually used.

At Line 714-723, only is_ok/is_err is asserted. Add an observable assertion (e.g., book after update and verify deposit uses the new rate) to catch storage/write-path regressions.

🤖 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 700 - 724, The
test test_expert_can_set_and_update_rate currently only checks try_set_my_rate
return values; extend it to assert the updated rate is actually stored/used by
reading back the expert's rate (e.g., call a read method on the client like
get_rate or query storage for the expert) or by performing an action that uses
the rate (e.g., create a deposit/booking via the client and verify the computed
fee reflects the new 25_i128 rate) after the update; locate and update uses of
client.try_set_my_rate, client.init, and create_client to add the observable
assertion that the storage/write-path applied the new rate.
🤖 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/test.rs`:
- Around line 780-783: Replace the broad assert!(res.is_err()) after calling
client.try_book_session(&user, &expert, &max_duration) with a precise assertion
that the returned error matches the ExpertRateNotSet variant (e.g.,
pattern-match the Result/Err to ensure it is ExpertRateNotSet) and additionally
verify that no balances changed for the involved parties (capture balances/state
for user and expert before and after the call and assert they are equal) so the
test ensures the specific rejection path and no side-effects.

---

Nitpick comments:
In `@contracts/payment-vault-contract/src/test.rs`:
- Around line 273-276: Extract the repeated "set rate + book" sequence into a
small helper function (e.g., fn set_rate_and_book(client: &mut Client, user:
&User, expert: &Expert, rate: &Rate, max_duration: &Duration) -> BookingId) that
calls client.set_my_rate(&expert, &rate_per_second) then
client.book_session(&user, &expert, &max_duration) and returns the booking id;
replace the inline block that assigns booking_id with a call to
set_rate_and_book to reduce duplication across tests and make intent clearer
(update all occurrences of client.set_my_rate and client.book_session to use
this new helper).
- Around line 700-724: The test test_expert_can_set_and_update_rate currently
only checks try_set_my_rate return values; extend it to assert the updated rate
is actually stored/used by reading back the expert's rate (e.g., call a read
method on the client like get_rate or query storage for the expert) or by
performing an action that uses the rate (e.g., create a deposit/booking via the
client and verify the computed fee reflects the new 25_i128 rate) after the
update; locate and update uses of client.try_set_my_rate, client.init, and
create_client to add the observable assertion that the storage/write-path
applied the new rate.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 063eb26 and 567d5e4.

📒 Files selected for processing (1)
  • contracts/payment-vault-contract/src/test.rs

Comment on lines +780 to +783
let res = client.try_book_session(&user, &expert, &max_duration);

assert!(res.is_err());
}
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

The unset-rate failure assertion is too broad.

assert!(res.is_err()) can pass for unrelated failures. Assert the specific ExpertRateNotSet path (and ideally no balance movement) to lock this acceptance criterion.

🤖 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 780 - 783, Replace
the broad assert!(res.is_err()) after calling client.try_book_session(&user,
&expert, &max_duration) with a precise assertion that the returned error matches
the ExpertRateNotSet variant (e.g., pattern-match the Result/Err to ensure it is
ExpertRateNotSet) and additionally verify that no balances changed for the
involved parties (capture balances/state for user and expert before and after
the call and assert they are equal) so the test ensures the specific rejection
path and no side-effects.

@dinahmaccodes
Copy link
Contributor Author

Fixing and adjusting based on review once more

@dinahmaccodes
Copy link
Contributor Author

@Bosun-Josh121
I got everything to pass!!
Please review when you are free

@Bosun-Josh121 Bosun-Josh121 merged commit 918fe12 into LightForgeHub:main Feb 25, 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 On-Chain Expert Pricing

2 participants