Skip to content

refactor(storage): fix unbounded vector growth with O(1) indexed storage (#24)#46

Merged
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
rocknwa:fix/unbounded-vector-growth-24
Mar 26, 2026
Merged

refactor(storage): fix unbounded vector growth with O(1) indexed storage (#24)#46
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
rocknwa:fix/unbounded-vector-growth-24

Conversation

@rocknwa
Copy link
Copy Markdown
Contributor

@rocknwa rocknwa commented Mar 24, 2026

🛠 Fix: Unbounded Vector Growth (Gas Optimization)

This PR resolves issue #24 by replacing Vec-based booking storage with a scalable, gas-efficient indexed storage model.


🚨 Problem

Previously, user and expert bookings were stored as:

  • UserBookings(Address) -> Vec<u64>
  • ExpertBookings(Address) -> Vec<u64>

Each new booking required:

  • Loading entire vector
  • Appending new item
  • Writing entire vector back

This results in O(N) storage operations, which:

  • Scales poorly with user history
  • Risks exceeding Soroban gas limits
  • Can permanently break contract usage for heavy users

✅ Solution

🔹 Storage Refactor (O(1) Writes)

Replaced vector storage with indexed keys:

  • UserBooking(Address, u32)
  • UserBookingCount(Address)
  • ExpertBooking(Address, u32)
  • ExpertBookingCount(Address)

Now:

  • Each booking write = 2 constant-time operations
  • No large memory loads

🔹 Pagination Support

Updated interface:

get_user_bookings(env, user, start_index, limit)
get_expert_bookings(env, expert, start_index, limit)

Added:

  • get_user_booking_count
  • get_expert_booking_count

This allows safe querying of large datasets.


🔹 Tests Added

  • ✅ 50 bookings for a single user (scale test)
  • ✅ Pagination correctness (multiple pages + edge cases)
  • ✅ Multi-user isolation
  • ✅ Expert-side pagination

🎯 Result

  • ✅ Eliminates unbounded vector growth
  • ✅ Prevents gas limit failures
  • ✅ Enables infinite scalability
  • ✅ Achieves O(1) storage writes
  • ✅ Safe frontend pagination

📌 Files Changed

  • storage.rs — core refactor
  • lib.rs — interface update
  • test.rs — new tests

🔗 Issue

Closes #24
image

Summary by CodeRabbit

  • New Features

    • Added methods to retrieve booking counts for users and experts with constant-time retrieval.
    • Introduced pagination support for booking retrieval endpoints.
  • Documentation

    • Enhanced contract API documentation with improved clarity and formatting.
  • Refactor

    • Restructured booking retrieval to support paginated results instead of all-at-once fetching.

…storage

- Remove UserBookings and ExpertBookings Vec storage (unbounded growth risk)
- Introduce composite keys: UserBooking, UserBookingCount, ExpertBooking, ExpertBookingCount
- Refactor add_booking_* to constant-time writes (no full vector rewrite)
- Add paginated getters for user and expert bookings
- Update contract interface to support pagination (start_index, limit)
- Add booking count getters for efficient frontend querying
- Add tests for 50-booking scale and pagination correctness

Fixes LightForgeHub#24
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

The PR refactors booking list storage from unbounded vectors to indexed keys with O(1) write semantics. Storage now uses composite keys for individual booking entries paired with counters, eliminating gas-expensive vector rewriting. Pagination APIs replace bulk retrieval to support safe frontend queries across large booking histories.

Changes

Cohort / File(s) Summary
Contract API
contracts/payment-vault-contract/src/lib.rs
Updated get_user_bookings and get_expert_bookings to accept pagination parameters (start_index, limit). Added new read-only methods get_user_booking_count and get_expert_booking_count for O(1) count retrieval. Documentation refined with trailing periods.
Storage Layer
contracts/payment-vault-contract/src/storage.rs
Replaced composite Vec<u64> storage (UserBookings, ExpertBookings) with indexed keys UserBooking(Address, u32) and counters UserBookingCount(Address). Refactored list mutations to O(1) writes. Added paginated retrieval functions get_user_bookings_paginated and get_expert_bookings_paginated; removed bulk retrieval variants.
Tests
contracts/payment-vault-contract/src/test.rs
Updated existing tests to use pagination parameters and added count assertions. Introduced three new scale/pagination tests: single-user 50-booking pagination, isolation between users, and expert pagination across multiple users.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feat/payment vault enhancements #19: Introduced the original booking list storage and add_booking_to_*_list helpers that this PR optimizes into indexed, paginated storage for gas efficiency.

Poem

🐰 Hop, hop—vectors once grew without bound,
But now our indexed keys keep gas firmly sound!
With pagination's hop and O(1) writes so bright,
A thousand bookings bloom without a fright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the main change: refactoring storage to fix unbounded vector growth with O(1) indexed storage, directly matching the PR's core objective.
Description check ✅ Passed The description covers the problem, solution with storage refactor and pagination, tests added, and results. However, it does not explicitly check all template items like issue number link, evidence attachment, or 'Ran cargo test' confirmation.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #24: refactored storage with O(1) indexed keys [#24], added pagination to getters [#24], implemented count helpers [#24], and added comprehensive pagination tests [#24].
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #24 objectives. Storage refactor, pagination interface, count helpers, and new tests address the stated problem without introducing unrelated modifications.
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

🤖 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/storage.rs`:
- Around line 126-149: The functions get_user_bookings_paginated and
get_expert_bookings_paginated allow an unbounded caller-controlled limit and can
overflow when computing start_index + limit; define a contract-level constant
(e.g. MAX_PAGE_SIZE) and clamp the incoming limit to that (limit =
limit.min(MAX_PAGE_SIZE)), then compute the end using a safe add (e.g.
start_index.saturating_add(limit) or
start_index.checked_add(limit).unwrap_or_max()) before taking min(count) to
avoid overflow; apply these changes to the functions named
get_user_bookings_paginated and get_expert_bookings_paginated and ensure the
clamped limit is used when iterating and pushing booking ids.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e95b68f0-7c71-4527-8a41-dfc32bc3cea1

📥 Commits

Reviewing files that changed from the base of the PR and between 01a3a5c and 80c3d22.

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

Comment on lines +126 to 149
pub fn get_user_bookings_paginated(
env: &Env,
user: &Address,
start_index: u32,
limit: u32,
) -> soroban_sdk::Vec<u64> {
let count = get_user_booking_count(env, user);
let mut result = soroban_sdk::Vec::new(env);

let end = (start_index + limit).min(count);
let mut i = start_index;
while i < end {
if let Some(booking_id) = env
.storage()
.persistent()
.get::<DataKey, u64>(&DataKey::UserBooking(user.clone(), i))
{
result.push_back(booking_id);
}
i += 1;
}

result
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the current pagination math and whether release overflow checks are configured.
rg -n 'get_(user|expert)_bookings_paginated|start_index\s*\+\s*limit|saturating_add|checked_add|MAX_BOOKINGS_PAGE_SIZE' contracts/payment-vault-contract/src

fd '^Cargo.toml$' | while read -r file; do
  echo "--- $file"
  sed -n '/\[profile.release\]/,/^\[/p' "$file"
done

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1503


Implement explicit pagination size bounds.

The limit parameter is fully caller-controlled, allowing requests for arbitrarily large pages. While start_index + limit arithmetic will panic on overflow in release builds (overflow-checks enabled in Cargo.toml), relying on panics is not idiomatic defense—explicit bounds should be enforced instead. Clamp limit to a contract-defined maximum to prevent DoS and excessive resource consumption.

This applies to both get_user_bookings_paginated (line 126–149) and get_expert_bookings_paginated (line 174–197).

🛠️ Proposed fix
+const MAX_BOOKINGS_PAGE_SIZE: u32 = 100;
+
 pub fn get_user_bookings_paginated(
     env: &Env,
     user: &Address,
     start_index: u32,
     limit: u32,
 ) -> soroban_sdk::Vec<u64> {
     let count = get_user_booking_count(env, user);
     let mut result = soroban_sdk::Vec::new(env);
 
-    let end = (start_index + limit).min(count);
+    let end = start_index
+        .saturating_add(limit.min(MAX_BOOKINGS_PAGE_SIZE))
+        .min(count);
     let mut i = start_index;
     while i < end {
         if let Some(booking_id) = env
             .storage()
             .persistent()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/payment-vault-contract/src/storage.rs` around lines 126 - 149, The
functions get_user_bookings_paginated and get_expert_bookings_paginated allow an
unbounded caller-controlled limit and can overflow when computing start_index +
limit; define a contract-level constant (e.g. MAX_PAGE_SIZE) and clamp the
incoming limit to that (limit = limit.min(MAX_PAGE_SIZE)), then compute the end
using a safe add (e.g. start_index.saturating_add(limit) or
start_index.checked_add(limit).unwrap_or_max()) before taking min(count) to
avoid overflow; apply these changes to the functions named
get_user_bookings_paginated and get_expert_bookings_paginated and ensure the
clamped limit is used when iterating and pushing booking ids.

@Bosun-Josh121 Bosun-Josh121 merged commit b243cbc into LightForgeHub:main Mar 26, 2026
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 26, 2026
9 tasks
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.

Fix Unbounded Vector Growth (Gas Optimization)

2 participants