Skip to content
Merged
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
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ This must produce zero warnings.
- No `unsafe` code is permitted in any contract.
- No external crate dependencies beyond `soroban-sdk` are permitted without prior discussion with maintainers.

### Storage Type Selection

Soroban provides three storage tiers. When adding a new `DataKey` variant to any contract, pick the right one:

| Storage type | Use when… |
| :--- | :--- |
| `instance()` | Small scalars (counters, config) that are read on almost every call and can share the contract instance TTL |
| `persistent()` | Per-user or per-entity data (streams, vesting configs, proposals) that must survive beyond the instance TTL |
| `temporary()` | Short-lived data that can expire without consequence (e.g. nonces, rate-limit windows) |

Always add a comment above the variant in the `DataKey` enum documenting which storage type it uses and why. See `contracts/forge-stream/src/lib.rs` for an example.

---

## Pre-Commit Hook (Optional but Recommended)
Expand Down
23 changes: 23 additions & 0 deletions contracts/forge-stream/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Forge Stream

## Storage Strategy

`forge-stream` uses two Soroban storage tiers. The rule of thumb: use
**persistent** for data that must survive beyond a single transaction or
contract instance TTL; use **instance** for small, frequently-accessed
scalars that are always read together with the contract instance.

| `DataKey` variant | Storage type | Rationale |
| :--- | :--- | :--- |
| `Stream(u64)` | `persistent` | Stream data must outlive the instance TTL while tokens remain unclaimed |
| `NextId` | `instance` | Small scalar always read on `create_stream`; co-located with instance for efficiency |
| `ActiveStreamsCount` | `instance` | Updated on every create/cancel/finish; always accessed with other instance data |
| `SenderStreams(Address)` | `persistent` | Grows with each stream; must survive beyond instance TTL for historical lookups |
| `RecipientStreams(Address)` | `persistent` | Same rationale as `SenderStreams` |

When adding a new `DataKey` variant, choose the storage type using this
checklist:
- Does the data need to survive after the contract instance TTL expires? → **persistent**
- Is it a small scalar read on almost every call? → **instance**
- Is it keyed per-user or per-stream (unbounded growth)? → **persistent**

---

## Resource Usage

> **Note:** Resource usage estimates are approximate and may vary based on contract state and input sizes. Run `stellar contract invoke` with `--cost` flag to measure actual usage for your specific use case.
Expand Down
14 changes: 14 additions & 0 deletions contracts/forge-stream/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@ use soroban_sdk::{

#[contracttype]
pub enum DataKey {
/// Per-stream data (token, sender, recipient, rate, timestamps, state).
/// Uses **persistent** storage — must outlive the contract instance TTL
/// for as long as the stream has unclaimed tokens.
Stream(u64),
/// Monotonically increasing counter used to assign the next stream ID.
/// Uses **instance** storage — small scalar that is always read on
/// `create_stream`, so co-locating it with the instance is efficient.
NextId,
/// Count of streams that are currently active (not cancelled/finished).
/// Uses **instance** storage — updated on every create/cancel/finish,
/// always accessed together with other instance data.
ActiveStreamsCount,
/// List of stream IDs created by a given sender address.
/// Uses **persistent** storage — the list grows with each stream and
/// must survive beyond the instance TTL for historical lookups.
SenderStreams(Address),
/// List of stream IDs where a given address is the recipient.
/// Uses **persistent** storage — same rationale as `SenderStreams`.
RecipientStreams(Address),
}

Expand Down
Loading