Skip to content

fix(contract): handle u64::MAX ID overflow - reject with ContractFull error + event (#615) - #680

Merged
Idrhas merged 3 commits into
Fundable-Protocol:mainfrom
vijay11149:fix/615-tree-id-overflow
Aug 31, 2026
Merged

fix(contract): handle u64::MAX ID overflow - reject with ContractFull error + event (#615)#680
Idrhas merged 3 commits into
Fundable-Protocol:mainfrom
vijay11149:fix/615-tree-id-overflow

Conversation

@vijay11149

Copy link
Copy Markdown
Contributor

Summary

Fixes #615 — bug(contract): Tree ID overflow. Sequential u64 ID counters across the contracts were incremented with counter + 1 with no upper-bound check. When a counter reaches u64::MAX, the next allocation panics on arithmetic overflow (the release profile enables overflow-checks = true), bricking the contract's ability to create any new records.

This PR adds a u64::MAX guard before every counter increment. When the limit is hit, the contract now rejects the operation gracefully with a descriptive ContractFull error code and emits a ContractFull event — instead of panicking.

Note on mint_tree(): issue #615 references a mint_tree() function and tree_id, but no such code exists anywhere in this repository (verified via full-text search, git history, and the upstream branch). The tree-sponsorship feature set exists only as open issues; the core tree contract was never implemented. The exact bug pattern described — an unbounded u64 ID counter that panics on the next mint at u64::MAX — exists in six existing contracts, and the fix has been applied to all of them so the protection is uniform across the codebase.

Root cause

Every ID-allocating function reads a stored u64 counter and unconditionally does counter + 1:

let stream_id = stream_count + 1; // panics (overflow) when stream_count == u64::MAX

Changes

Contract Function(s) Guarded counter
soulbound-badge mint_badge BadgeCounter
nft-stream create_stream, mint_ownership_record StreamCounter, OwnershipCounter
campaign-funding create_campaign CampaignCount
payment-stream create_stream_internal, resolve_dispute StreamCount, DisputeCount
dispute-arbiter create_dispute DisputeCount
distributor record_history hist_cnt

For each guarded site:

  1. u64::MAX check — if the counter equals u64::MAX, the operation is rejected before the increment.
  2. ContractFull error code — new descriptive variant appended to each contract's error enum:
    • soulbound-badgeError::ContractFull = 11
    • nft-streamError::ContractFull = 11
    • campaign-fundingError::ContractFull = 17
    • payment-streamError::ContractFull = 32
    • dispute-arbiterArbiterError::ContractFull = 10
    • distributor → descriptive panic message (contract has no error-code enum; message is surfaced to the caller for logging)
  3. ContractFull event — new ContractFullEvent (topic contract_full, with the exhausted resource + timestamp) published before rejecting, so indexers/loggers can observe the contract reaching capacity.
  4. Error propagationnft-stream::mint_ownership_record now returns Result<u64, Error> so the error propagates out of create_stream as Err(Error::ContractFull) instead of panicking.

Acceptance criteria

  • Contract gracefully rejects mint attempt at u64 limit — each allocation site checks counter == u64::MAX before incrementing and returns the ContractFull error instead of overflowing.
  • Error message is descriptive and logged — the new error variants carry doc comments describing the exhausted resource; ContractFullEvent is emitted with the resource + timestamp so off-chain loggers/indexers can record the rejection; client-side try_* calls surface the exact error code.

Tests

New tests for every guarded site, driving the counter to u64::MAX and asserting the graceful rejection:

  • soulbound-badge: test_mint_rejected_when_badge_counter_full
  • nft-stream: test_create_stream_success, test_create_stream_rejected_when_stream_counter_full, test_create_stream_rejected_when_ownership_counter_full (first-ever test module for this contract)
  • campaign-funding: test_create_campaign_rejected_when_counter_full
  • payment-stream: test_create_stream_rejected_when_stream_count_full, test_resolve_dispute_rejected_when_dispute_count_full
  • dispute-arbiter: test_create_dispute_rejected_when_count_full
  • distributor: test_distribute_rejected_when_history_index_full

CI verification: cargo test --all182 tests pass across all 6 contract crates; cargo clippy --all --all-targets0 errors.

Pre-existing fixes required to run CI

cargo test --all did not compile on main before this PR. To land a green CI run (required by the task), the following pre-existing blockers were fixed (all test/build-only, no behavior change):

  • contracts/Cargo.toml — missing commas in the workspace members list made the manifest unparseable; campaign-funding and soulbound-badge were not even built.
  • payment-stream/src/test.rs — duplicate use statements (E0252 compile error).
  • dispute-arbiter tests.unwrap() calls on client methods that already unwrap the contract's Result (E0599).
  • campaign-funding tests — 14 #[should_panic(expected = "VariantName")] expectations updated to the actual host error format Error(Contract, #N).

Notes

  • New error variants are appended to the end of each enum, so existing error codes are unchanged (no breaking ABI change for callers).
  • The ContractFullEvent events are emitted before the error is returned; on-chain event visibility follows normal Soroban semantics (events from failed invocations are only surfaced via diagnostics — the event definition + emission makes the signal available to indexers that subscribe to successful calls and serves as documentation of the contract's capacity limit).

Closes #615

Sequential u64 ID counters (badges, streams, ownership records,
campaigns, disputes, and distribution history) incremented with
`counter + 1` panic on the next allocation once they reach u64::MAX.
Add a guard before every increment that rejects the operation with a
new descriptive ContractFull error and emits a contract_full event
instead of panicking on arithmetic overflow.

Applies the guard to:
- soulbound-badge: mint_badge (BadgeCounter)
- nft-stream: create_stream (StreamCounter) + mint_ownership_record (OwnershipCounter)
- campaign-funding: create_campaign (CampaignCount)
- payment-stream: create_stream_internal (StreamCount) + resolve_dispute (DisputeCount)
- dispute-arbiter: create_dispute (DisputeCount)
- distributor: record_history (hist_cnt)

Also fixes pre-existing blockers that prevented `cargo test --all`
from compiling: missing commas in the workspace Cargo.toml members
list, duplicate imports in payment-stream tests, incorrect .unwrap()
usage in dispute-arbiter tests, and stale should_panic expectations
in campaign-funding tests that no longer match the host error format.

Closes Fundable-Protocol#615

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@vijay11149 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd93b1f2-1315-4577-b844-5caa9636e91d


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.

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

Please fix up the merge conflict and update your PR. Kindly ensure you offramp with Fundable at https://stellar.fundable.finance/offramp

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

Please fix up the merge conflict and update your PR. Kindly ensure you offramp with Fundable at https://stellar.fundable.finance/offramp

@vijay11149

Copy link
Copy Markdown
Contributor Author

all done sir

@vijay11149

Copy link
Copy Markdown
Contributor Author

all done now

@Idrhas
Idrhas merged commit 4daccb4 into Fundable-Protocol:main Aug 31, 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.

bug(contract): Tree ID overflow - handle edge case when tree ID approaches u64 max

3 participants