Skip to content

Commit 86473ea

Browse files
Merge pull request #65 from supreme2580/issue-18
Implement Cliff_Based_Access for Early Supporters
2 parents 7ecfb66 + 346d7bf commit 86473ea

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ target
44
# Local settings
55
.soroban
66
.stellar
7+
8+
# Test snapshots
9+
contracts/*/test_snapshots

contracts/substream_contracts/src/lib.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#![no_std]
2+
use soroban_sdk::token::Client as TokenClient;
3+
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};
24
use soroban_sdk::{ contract, contractimpl, contracttype, Address, Env };
35
use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, Vec};
46
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec};
@@ -11,6 +13,9 @@ const MINIMUM_FLOW_DURATION: u64 = 86400;
1113
#[contracttype]
1214
#[derive(Clone, Debug, Eq, PartialEq)]
1315
pub enum DataKey {
16+
Stream(Address, Address), // (subscriber, creator)
17+
TotalStreamed(Address, Address), // (subscriber, creator) - cumulative tokens streamed
18+
CliffThreshold(Address), // creator -> threshold amount for access
1419
Stream(Address, Address), // (subscriber, creator)
1520
CreatorSubscribers(Address), // creator -> Vec<subscriber>
1621
Stream(Address, Address), // (subscriber, stream_id)
@@ -169,6 +174,7 @@ fn subscribe_internal(
169174
stream.last_collected = current_time;
170175

171176
env.storage().persistent().set(&key, &stream);
177+
Self::update_total_streamed(&env, &subscriber, &creator, amount_to_collect);
172178
}
173179
fn collect_internal(env: &Env, subscriber: &Address, stream_id: &Address) {
174180
let key = stream_key(subscriber, stream_id);
@@ -500,6 +506,63 @@ impl SubStreamContract {
500506

501507
total_collected
502508
}
509+
510+
pub fn set_cliff_threshold(env: Env, creator: Address, threshold: i128) {
511+
creator.require_auth();
512+
if threshold < 0 {
513+
panic!("threshold must be non-negative");
514+
}
515+
let key = DataKey::CliffThreshold(creator.clone());
516+
env.storage().persistent().set(&key, &threshold);
517+
}
518+
519+
pub fn get_cliff_threshold(env: Env, creator: Address) -> i128 {
520+
let key = DataKey::CliffThreshold(creator.clone());
521+
env.storage().persistent().get(&key).unwrap_or(0)
522+
}
523+
524+
pub fn get_total_streamed(env: Env, subscriber: Address, creator: Address) -> i128 {
525+
let key = DataKey::TotalStreamed(subscriber.clone(), creator.clone());
526+
env.storage().persistent().get(&key).unwrap_or(0)
527+
}
528+
529+
pub fn has_unlocked_access(env: Env, subscriber: Address, creator: Address) -> bool {
530+
let threshold_key = DataKey::CliffThreshold(creator.clone());
531+
let threshold: i128 = env.storage().persistent().get(&threshold_key).unwrap_or(0);
532+
if threshold == 0 {
533+
return true;
534+
}
535+
let streamed_key = DataKey::TotalStreamed(subscriber.clone(), creator.clone());
536+
let total_streamed: i128 = env.storage().persistent().get(&streamed_key).unwrap_or(0);
537+
total_streamed >= threshold
538+
}
539+
540+
pub fn get_access_tier(env: Env, subscriber: Address, creator: Address) -> u32 {
541+
let threshold_key = DataKey::CliffThreshold(creator.clone());
542+
let threshold: i128 = env.storage().persistent().get(&threshold_key).unwrap_or(0);
543+
if threshold == 0 {
544+
return 2;
545+
}
546+
let streamed_key = DataKey::TotalStreamed(subscriber.clone(), creator.clone());
547+
let total_streamed: i128 = env.storage().persistent().get(&streamed_key).unwrap_or(0);
548+
549+
if total_streamed >= 500 {
550+
3
551+
} else if total_streamed >= 200 {
552+
2
553+
} else if total_streamed >= 50 {
554+
1
555+
} else {
556+
0
557+
}
558+
}
559+
560+
fn update_total_streamed(env: &Env, subscriber: &Address, creator: &Address, amount: i128) {
561+
let key = DataKey::TotalStreamed(subscriber.clone(), creator.clone());
562+
let current_total: i128 = env.storage().persistent().get(&key).unwrap_or(0);
563+
let new_total = current_total + amount;
564+
env.storage().persistent().set(&key, &new_total);
565+
}
503566
}
504567

505568
mod test;

contracts/substream_contracts/src/test.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#![cfg(test)]
22

33
use super::*;
4+
use soroban_sdk::{
5+
testutils::{Address as _, Ledger},
6+
token, Address, Env,
7+
};
48
use soroban_sdk::{ testutils::{ Address as _, Ledger }, token, Address, Env };
59
use soroban_sdk::testutils::{Address as _, Events as _, Ledger};
610
use soroban_sdk::{token, Address, Env, Event};
@@ -157,6 +161,7 @@ fn test_top_up() {
157161
}
158162

159163
#[test]
164+
fn test_cliff_based_access_no_threshold() {
160165
#[should_panic(expected = "cannot cancel stream: minimum duration not met")]
161166
fn test_cancel_before_minimum_duration() {
162167

@@ -188,6 +193,12 @@ fn test_group_subscribe_and_collect_split() {
188193
let contract_id = env.register(SubStreamContract, ());
189194
let client = SubStreamContractClient::new(&env, &contract_id);
190195

196+
assert!(client.has_unlocked_access(&subscriber, &creator));
197+
assert_eq!(client.get_access_tier(&subscriber, &creator), 2);
198+
}
199+
200+
#[test]
201+
fn test_cliff_based_access_before_threshold() {
191202
// Subscribe at timestamp 100
192203
env.ledger().set_timestamp(100);
193204
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
@@ -275,6 +286,22 @@ fn test_group_cancel_collects_and_refunds_remaining_balance() {
275286
let contract_id = env.register(SubStreamContract, ());
276287
let client = SubStreamContractClient::new(&env, &contract_id);
277288

289+
client.set_cliff_threshold(&creator, &50);
290+
assert_eq!(client.get_cliff_threshold(&creator), 50);
291+
292+
assert!(!client.has_unlocked_access(&subscriber, &creator));
293+
assert_eq!(client.get_access_tier(&subscriber, &creator), 0);
294+
295+
client.subscribe(&subscriber, &creator, &token.address, &30, &1);
296+
env.ledger().set_timestamp(100);
297+
client.collect(&subscriber, &creator);
298+
299+
assert!(!client.has_unlocked_access(&subscriber, &creator));
300+
assert_eq!(client.get_access_tier(&subscriber, &creator), 0);
301+
}
302+
303+
#[test]
304+
fn test_cliff_based_access_after_threshold() {
278305
// Subscribe at timestamp 100
279306
env.ledger().set_timestamp(100);
280307
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
@@ -323,6 +350,22 @@ fn test_migrate_tier_upgrade_with_additional_deposit() {
323350
let contract_id = env.register(SubStreamContract, ());
324351
let client = SubStreamContractClient::new(&env, &contract_id);
325352

353+
client.set_cliff_threshold(&creator, &50);
354+
355+
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
356+
357+
assert!(!client.has_unlocked_access(&subscriber, &creator));
358+
359+
env.ledger().set_timestamp(100);
360+
client.collect(&subscriber, &creator);
361+
362+
assert!(client.has_unlocked_access(&subscriber, &creator));
363+
assert_eq!(client.get_total_streamed(&subscriber, &creator), 100);
364+
assert_eq!(client.get_access_tier(&subscriber, &creator), 1);
365+
}
366+
367+
#[test]
368+
fn test_access_tiers() {
326369
env.ledger().set_timestamp(100);
327370
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
328371

@@ -385,11 +428,39 @@ fn test_group_requires_exactly_five_creators() {
385428

386429
let token = create_token_contract(&env, &admin);
387430
let token_admin = token::StellarAssetClient::new(&env, &token.address);
431+
token_admin.mint(&subscriber, &10000);
388432
token_admin.mint(&subscriber, &1000);
389433

390434
let contract_id = env.register(SubStreamContract, ());
391435
let client = SubStreamContractClient::new(&env, &contract_id);
392436

437+
client.set_cliff_threshold(&creator, &50);
438+
439+
client.subscribe(&subscriber, &creator, &token.address, &60, &2);
440+
441+
env.ledger().set_timestamp(100);
442+
client.collect(&subscriber, &creator);
443+
444+
assert_eq!(client.get_access_tier(&subscriber, &creator), 1);
445+
assert!(client.has_unlocked_access(&subscriber, &creator));
446+
447+
client.top_up(&subscriber, &creator, &200);
448+
env.ledger().set_timestamp(200);
449+
client.collect(&subscriber, &creator);
450+
451+
assert_eq!(client.get_access_tier(&subscriber, &creator), 2);
452+
assert!(client.has_unlocked_access(&subscriber, &creator));
453+
454+
client.top_up(&subscriber, &creator, &300);
455+
env.ledger().set_timestamp(400);
456+
client.collect(&subscriber, &creator);
457+
458+
assert_eq!(client.get_access_tier(&subscriber, &creator), 3);
459+
assert!(client.has_unlocked_access(&subscriber, &creator));
460+
}
461+
462+
#[test]
463+
fn test_total_streamed_tracking() {
393464
// Subscribe at timestamp 100
394465
env.ledger().set_timestamp(100);
395466
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
@@ -485,6 +556,16 @@ fn test_group_percentages_must_sum_to_100() {
485556
let contract_id = env.register(SubStreamContract, ());
486557
let client = SubStreamContractClient::new(&env, &contract_id);
487558

559+
client.subscribe(&subscriber, &creator, &token.address, &100, &1);
560+
561+
env.ledger().set_timestamp(100);
562+
client.collect(&subscriber, &creator);
563+
assert_eq!(client.get_total_streamed(&subscriber, &creator), 100);
564+
565+
client.top_up(&subscriber, &creator, &50);
566+
env.ledger().set_timestamp(150);
567+
client.collect(&subscriber, &creator);
568+
assert_eq!(client.get_total_streamed(&subscriber, &creator), 150);
488569
// Subscribe at timestamp 100
489570
env.ledger().set_timestamp(100);
490571
client.subscribe(&subscriber, &creator, &token.address, &100, &1);

0 commit comments

Comments
 (0)