Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/factory/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ pub enum Error {
/// is disabled there is no way to recover the funds before the stream
/// starts.
StartTimeTooFarInFuture = 29,
/// `stream_addresses` was called with more IDs than `MAX_RESOLVE_SIZE`.
ResolveTooLarge = 30,
}
6 changes: 3 additions & 3 deletions contracts/factory/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,12 @@ pub fn migrate_recipient_index(env: &Env, recipient: Address, max_pages: u32) ->
.unwrap_or_else(|| env.storage().persistent().get(&count_key).unwrap_or(0))
}

pub fn streams_by_sender(env: &Env, sender: Address, offset: u32, limit: u32) -> Vec<u64> {
pub fn streams_by_sender(env: &Env, sender: Address, offset: u32, limit: u32) -> StreamPage {
let count_key = DataKey::BySenderCount(sender.clone());
let legacy_key = DataKey::BySender(sender.clone());
let cursor_key = DataKey::BySenderMigrationCursor(sender.clone());
let legacy_count_key = DataKey::BySenderLegacyCount(sender.clone());
read_index(
let ids = read_index(
env,
&count_key,
&legacy_key,
Expand All @@ -384,7 +384,7 @@ pub fn streams_by_recipient(env: &Env, recipient: Address, offset: u32, limit: u
let legacy_key = DataKey::ByRecipient(recipient.clone());
let cursor_key = DataKey::ByRecipientMigrationCursor(recipient.clone());
let legacy_count_key = DataKey::ByRecipientLegacyCount(recipient.clone());
read_index(
let ids = read_index(
env,
&count_key,
&legacy_key,
Expand Down
21 changes: 12 additions & 9 deletions contracts/factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use storage::DataKey;
pub use storage::{BatchStreamRequest, FactoryStatus, FeeEstimate, StreamOperation, StreamPage};

/// Maximum number of streams accepted by a single `create_batch_streams`
/// (and `cancel_batch_streams`/`stream_addresses`) call. Each
/// (and `cancel_batch_streams`) call. Each
/// `create_stream` in the batch performs a governor cross-contract call,
/// two `token::transfer`s, a contract deploy + `initialize` invoke, and
/// three persistent writes with TTL extensions (~2.5M CPU instructions).
Expand Down Expand Up @@ -367,15 +367,18 @@ impl DripFactory {

/// Batch-resolve stream IDs to their deployed contract addresses.
///
/// Pairs with `streams_by_sender`/`streams_by_recipient`: a page of IDs
/// from either can be resolved to addresses in one call instead of one
/// `stream_address` round-trip per ID. Unknown IDs resolve to `None` in
/// their slot, matching `stream_address`'s per-ID behavior, rather than
/// failing the whole batch. Capped at `MAX_BATCH_SIZE`, mirroring
/// `create_batch_streams`.
/// Pairs with `streams_by_sender`/`streams_by_recipient`: a full page of
/// IDs from either (up to [`query::MAX_PAGE_SIZE`]) can be resolved to
/// addresses in one call instead of one `stream_address` round-trip per
/// ID. Unknown IDs resolve to `None` in their slot, matching
/// `stream_address`'s per-ID behavior, rather than failing the whole
/// batch. Capped at [`query::MAX_RESOLVE_SIZE`] (100), sized for the
/// read path — this function only performs `persistent().get()` lookups
/// and never deploys contracts or transfers tokens, so the write-path
/// [`MAX_BATCH_SIZE`] does not apply.
pub fn stream_addresses(env: Env, ids: Vec<u64>) -> Result<Vec<Option<Address>>, Error> {
if ids.len() > MAX_BATCH_SIZE {
return Err(Error::BatchTooLarge);
if ids.len() > query::MAX_RESOLVE_SIZE {
return Err(Error::ResolveTooLarge);
}
let mut out = Vec::new(&env);
for id in ids.iter() {
Expand Down
9 changes: 9 additions & 0 deletions contracts/factory/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ use soroban_sdk::{Env, Vec};
/// sender's entire history in a single view call.
pub const MAX_PAGE_SIZE: u32 = 100;

/// Hard cap for batch-resolving stream IDs to addresses.
///
/// Sized to match [`MAX_PAGE_SIZE`] so that a full page returned by
/// `streams_by_sender` / `streams_by_recipient` (up to 100 IDs) can be
/// resolved in a single `stream_addresses` call. This is a read-only
/// operation (`persistent().get()` per ID) with no deployment or token
/// transfer, so the write-path [`MAX_BATCH_SIZE`] does not apply here.
pub const MAX_RESOLVE_SIZE: u32 = 100;

/// Returns a paginated slice of `v` starting at `offset` with at most `limit`
/// elements.
///
Expand Down
14 changes: 7 additions & 7 deletions contracts/factory/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ fn legacy_sender_index_migration_is_incremental() {
});

let page = s.client.streams_by_sender(&sender, &95, &10);
assert_eq!(page.len(), 10);
assert_eq!(page.get(0).unwrap(), 95);
assert_eq!(page.get(9).unwrap(), 104);
assert_eq!(page.ids.len(), 10);
assert_eq!(page.ids.get(0).unwrap(), 95);
assert_eq!(page.ids.get(9).unwrap(), 104);

assert_eq!(s.client.migrate_sender_index(&sender, &10), 250);
s.env.as_contract(&s.client.address, || {
Expand Down Expand Up @@ -317,10 +317,10 @@ fn append_during_partial_sender_migration_preserves_order() {
assert_eq!(s.client.stream_count_by_sender(&sender), 151);

let tail = s.client.streams_by_sender(&sender, &145, &10);
assert_eq!(tail.len(), 6);
assert_eq!(tail.get(0).unwrap(), 145);
assert_eq!(tail.get(4).unwrap(), 149);
assert_eq!(tail.get(5).unwrap(), 999);
assert_eq!(tail.ids.len(), 6);
assert_eq!(tail.ids.get(0).unwrap(), 145);
assert_eq!(tail.ids.get(4).unwrap(), 149);
assert_eq!(tail.ids.get(5).unwrap(), 999);

assert_eq!(s.client.migrate_sender_index(&sender, &10), 151);
let tail_after = s.client.streams_by_sender(&sender, &145, &10);
Expand Down
6 changes: 6 additions & 0 deletions contracts/stream/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ pub enum Error {
ReentrancyForbidden = 16,
OperatorAlreadySet = 17,
NotInitialized = 18,
/// The recipient is invalid (e.g. the all-zero Stellar account address, or identical to `sender`).
InvalidRecipient = 19,
/// The stream's `start_time` is in the past at initialization.
BackdatedStream = 20,
/// The stream has accrued tokens but is not funded enough to cover the requested withdrawal.
StreamUnderfunded = 21,
}