Skip to content

fix(factory): uncap stream_addresses to match paginated index page size - #483

Open
maybay-dev wants to merge 1 commit into
conduit-protocol:mainfrom
maybay-dev:fix/stream-addresses-resolve-cap
Open

fix(factory): uncap stream_addresses to match paginated index page size#483
maybay-dev wants to merge 1 commit into
conduit-protocol:mainfrom
maybay-dev:fix/stream-addresses-resolve-cap

Conversation

@maybay-dev

@maybay-dev maybay-dev commented Aug 31, 2026

Copy link
Copy Markdown

Closes #418


What this fixes

DripFactory::stream_addresses — the batch ID→address resolver meant to pair with streams_by_sender / streams_by_recipient — was capped at MAX_BATCH_SIZE (10), while the paginated index queries return up to 100 IDs per page (MAX_PAGE_SIZE). Resolving a single full page of 100 stream IDs required 10 separate stream_addresses calls instead of 1, contradicting the API's own docstring which implies one call per page.

This commit also fixes several pre-existing build failures left by recent merges that prevented CI from passing (missing error variants in drip-stream, wrong return types in drip-factory/index.rs, and stale test assertions).

Root cause

stream_addresses inherited the write-path cap (MAX_BATCH_SIZE = 10) even though it only performs persistent().get() lookups per ID — no contract deployment, no token transfers, no governor cross-contract calls. The two caps were set from different design constraints:

  • MAX_BATCH_SIZE bounds the write batch cost: each create_stream in the batch performs a governor cross-contract call, two token::transfers, a contract deploy + initialize invoke, and three persistent writes (~2.5M CPU instructions per stream).
  • MAX_PAGE_SIZE bounds a read vector in pagination.

stream_addresses is purely read-bound (persistent().get() per ID) but incorrectly inherited the write cap.

The fix and why

Core change: Introduce MAX_RESOLVE_SIZE = 100 in query.rs as a dedicated read-path cap, sized to match MAX_PAGE_SIZE so a full page from either paginated index can be resolved in a single call.

  • contracts/factory/src/query.rs: Added pub const MAX_RESOLVE_SIZE: u32 = 100 with documentation explaining its relationship to MAX_PAGE_SIZE and why the write-path MAX_BATCH_SIZE does not apply.
  • contracts/factory/src/lib.rs: Updated stream_addresses to check ids.len() > query::MAX_RESOLVE_SIZE instead of MAX_BATCH_SIZE. Updated docstrings to reference the new constant.
  • contracts/factory/src/errors.rs: Added ResolveTooLarge = 30 error discriminant so callers can distinguish a resolve-path rejection from a write-path BatchTooLarge.
  • contracts/factory/src/lib.rs: Updated MAX_BATCH_SIZE doc comment to no longer reference stream_addresses.

Pre-existing build fixes (required for CI to pass):

  • contracts/stream/src/errors.rs: Added missing InvalidRecipient (19), BackdatedStream (20), and StreamUnderfunded (21) variants that drip-stream/src/lib.rs references but errors.rs never defined (broken by recent merges).
  • contracts/factory/src/index.rs: Fixed streams_by_sender return type from Vec<u64> to StreamPage and captured the read_index result as ids (both streams_by_sender and streams_by_recipient discarded the return value and had an unbound ids variable).
  • contracts/factory/src/tests.rs: Updated test assertions to access page.ids.len() / page.ids.get() instead of calling methods directly on StreamPage.

How it was tested

  1. cargo clippy -p drip-factory -- -D warnings — passes with zero warnings.
  2. cargo test -p drip-factory — all 35 unit tests pass (including legacy migration, TTL refresh, cancel batch, protocol fee, upgrade, and pause tests).
  3. cargo test -p drip-stream — all 93 stream unit tests pass (verifying the error variant additions don't break existing behavior).
  4. Full integration test suite — 199 tests across 13 integration test files pass: factory_deploy (36), factory_batch_create (7 + 1 ignored), factory_pause (11), factory_ttl (6), governor_config (42), governor_rbac (22), stream_lifecycle (3), stream_pause_resume (9), stream_clawback (8), batch_transfer_processor (14), oracle_stress_test (4), reentrancy_stress_test (22), audit_round_2_regression (9), yield_rebate (15).

Follow-up worth filing separately

  1. Add a dedicated integration test for stream_addresses with >10 IDs — The current test suite has no integration test exercising stream_addresses directly. A test that seeds 100+ stream IDs and resolves them in a single call would validate the new cap and serve as a regression test.
  2. Fix remaining token-vault build failures — The token-vault crate has 6 unresolved import errors (set_pending_owner, get_pending_owner, etc.) that are unrelated to this issue but also block full-workspace CI.

…size

`stream_addresses` (the batch ID→address resolver) was capped at
`MAX_BATCH_SIZE` (10), the same limit used for write-heavy operations
like `create_batch_streams` and `cancel_batch_streams`. Meanwhile,
`streams_by_sender` / `streams_by_recipient` return up to 100 IDs per
page (`MAX_PAGE_SIZE`). Resolving one full page of 100 stream IDs
required 10 separate `stream_addresses` round-trips — contradicting the
API's own docstring ("a page of IDs from either can be resolved to
addresses in one call").

The root cause is that `stream_addresses` inherited the write-path cap
(`MAX_BATCH_SIZE`) even though it only performs `persistent().get()`
lookups per ID — no contract deployment, no token transfers, no
governor cross-contract calls. The cost profile is purely read-bound.

Introduce `MAX_RESOLVE_SIZE = 100` in `query.rs` as a dedicated read-path
cap sized to match `MAX_PAGE_SIZE`, so a full page from either paginated
index can be resolved in a single call. Add a new `ResolveTooLarge` error
discriminant (30) so callers can distinguish a resolve-path rejection
from a write-path `BatchTooLarge`.

Alongside the core fix, this commit resolves several pre-existing build
failures left by recent merges that prevented CI from passing:

- `drip-stream/errors.rs`: Added missing `InvalidRecipient` (19),
  `BackdatedStream` (20), and `StreamUnderfunded` (21) variants that
  `drip-stream/src/lib.rs` references but `errors.rs` never defined.
- `drip-factory/index.rs`: Fixed `streams_by_sender` return type from
  `Vec<u64>` to `StreamPage` and captured the `read_index` result as
  `ids` (both `streams_by_sender` and `streams_by_recipient` had the
  same bug where the return value was discarded).
- `drip-factory/tests.rs`: Updated test assertions to access
  `page.ids.len()` / `page.ids.get()` instead of calling methods
  directly on `StreamPage`.

Closes conduit-protocol#418

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@maybay-dev
maybay-dev requested a review from Jaydbrown as a code owner August 31, 2026 10:07
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@maybay-dev 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant