-
Notifications
You must be signed in to change notification settings - Fork 168
Add contract-level stream expiry and auto-complete logic #670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ pub struct Stream { | |
| pub canceled: bool, | ||
| pub paused: bool, | ||
| pub pause_started_at: Option<u64>, | ||
| pub completed: bool, | ||
|
|
||
| pub metadata: Option<Map<String, String>>, | ||
| } | ||
|
|
@@ -78,6 +79,15 @@ pub struct StreamCanceled { | |
| pub sender: Address, | ||
| } | ||
|
|
||
| #[contracttype] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct StreamCompleted { | ||
| pub stream_id: u64, | ||
| pub recipient: Address, | ||
| pub total_amount: i128, | ||
| pub completed_at: u64, | ||
| } | ||
|
|
||
| #[contracttype] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
|
|
||
|
|
@@ -213,6 +223,7 @@ impl StellarStreamContract { | |
| canceled: false, | ||
| paused: false, | ||
| pause_started_at: None, | ||
| completed: false, | ||
|
|
||
| metadata: metadata.clone(), | ||
| }; | ||
|
|
@@ -311,6 +322,7 @@ impl StellarStreamContract { | |
| canceled: false, | ||
| paused: false, | ||
| pause_started_at: None, | ||
| completed: false, | ||
| metadata: None, | ||
| }; | ||
|
|
||
|
|
@@ -361,7 +373,11 @@ impl StellarStreamContract { | |
| } | ||
|
|
||
| pub fn get_stream(env: Env, stream_id: u64) -> Stream { | ||
| read_stream(&env, stream_id) | ||
| let mut stream = read_stream(&env, stream_id); | ||
| if !stream.completed && !stream.canceled && env.ledger().timestamp() >= stream.end_time { | ||
| stream.completed = true; | ||
| } | ||
|
Comment on lines
+376
to
+379
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Honor paused time when deriving completion. A stream paused before 🤖 Prompt for AI Agents |
||
| stream | ||
| } | ||
|
|
||
| pub fn get_next_stream_id(env: Env) -> u64 { | ||
|
|
@@ -444,15 +460,33 @@ impl StellarStreamContract { | |
| token_client.transfer(&contract_address, &recipient, &amount); | ||
|
|
||
| stream.claimed_amount += amount; | ||
|
|
||
| let newly_completed = !stream.completed && !stream.canceled && now >= stream.end_time; | ||
| if newly_completed { | ||
| stream.completed = true; | ||
|
Comment on lines
+464
to
+466
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Make persisted completion a terminal state. A partial claim at
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| env.storage() | ||
| .persistent() | ||
| .set(&DataKey::Stream(stream_id), &stream); | ||
|
|
||
| env.events().publish( | ||
| (symbol_short!("Stream"), symbol_short!("Claimed")), | ||
| StreamClaimed { stream_id, recipient, amount }, | ||
| StreamClaimed { stream_id, recipient: recipient.clone(), amount }, | ||
| ); | ||
|
|
||
| if newly_completed { | ||
| env.events().publish( | ||
| (symbol_short!("Stream"), symbol_short!("Completed")), | ||
| StreamCompleted { | ||
| stream_id, | ||
| recipient, | ||
| total_amount: stream.total_amount, | ||
| completed_at: now, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| amount | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: ritik4ever/stellar-stream
Length of output: 50382
🏁 Script executed:
Repository: ritik4ever/stellar-stream
Length of output: 50382
🏁 Script executed:
Repository: ritik4ever/stellar-stream
Length of output: 50382
🌐 Web query:
Soroban rust-sdk contracttype contract storage serialization struct order add field breaking upgrade💡 Result:
In the Soroban Rust SDK, contract data is serialized to XDR using the
#[contracttype]macro [1][2]. Because Soroban contracts upgrade by replacing the WASM bytecode while leaving existing contract storage in-place, changing data structures is a high-risk operation [3]. Adding a field to a#[contracttype]struct or changing field ordering will alter the serialized XDR format, making the new WASM binary incompatible with data already stored in the ledger [3]. If your new contract attempts to deserialize existing storage using an updated struct definition, the following issues will likely occur: 1. Deserialization Failure: If the new struct has more fields than the stored data contains, the SDK will typically panic at runtime during deserialization [3]. 2. Silent Data Corruption: If the new struct has fewer fields, or if field types are modified (e.g., changing a type's underlying representation), the SDK may silently misinterpret the stored bytes, leading to data loss or state corruption [3]. Best Practices for Upgrades and Schema Evolution: - Manual Migration: There is no automatic migration callback in Soroban [3]. You must implement a dedicatedmigrate()entrypoint in your new WASM code to manually transform existing storage entries to the new schema [4][3][5]. - Atomic Upgrades: To avoid state inconsistencies, consider using a dedicated upgrader contract or pausing external access to the contract before and during the migration process [4][5]. - Version Tracking: Include a version identifier in your contract instance storage (e.g., usingcontractmeta!or a specificAppVersionkey) [4][6]. Your logic should check this version to determine if a migration is required before proceeding with normal operations [4][3]. - Append-Only Logic: If you must evolve storage, a common pattern is to avoid modifying existing fields. Instead, create new storage keys for new data or maintain legacy keys alongside new ones until the migration is complete [3]. - Rollback Strategy: Always define a rollback plan. Ensure your upgrade process leaves the contract in a state that can be restored or corrected if the new code fails [4][5]. Because storage entries persist through upgrades, you must ensure that any changes to your data schema are explicitly handled by your contract logic to prevent reading inconsistent or incompatible state [3][5].Citations:
Preserve compatibility with existing persisted streams.
Streamis a persisted#[contracttype]record; addingcompletedchanges its serialized on-chain format. ExistingDataKey::Streamentries written without this field can become undecodable after upgrade, so add an explicit migration/versioned schema path before deploying this contract.🤖 Prompt for AI Agents