diff --git a/Cargo.toml b/Cargo.toml index 4ee454ef..f0be738a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,6 @@ ed25519-dalek = "=2.0.0" [features] default = [] testutils = ["soroban-sdk/testutils"] + +[profile.release] +overflow-checks = true diff --git a/README.md b/README.md index 6aefab03..2f7b1602 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,11 @@ Auth failures (e.g. wrong signer) are signaled by host/panic, not `RevoraError`. ### Call patterns and limits - **Pagination:** Use `get_offerings_page(issuer, start, limit)` with `start = 0` then `start = next_cursor` until `next_cursor` is `None`. Max page size 20. Ordering: by registration index (creation order), deterministic. + - **Chunked read-only queries:** For long numeric ranges or unbounded per-holder lists, prefer the chunked helpers to avoid long-running loops: + - `get_revenue_range_chunk(env, issuer, namespace, token, from_period, to_period, max_periods)` — sums up to `max_periods` numeric period ids in [from_period, to_period], returns `(sum, next_start)` to continue. + - `get_pending_periods_page(env, issuer, namespace, token, holder, start, limit)` — returns a page of pending period IDs and a `next_cursor` if more remain. + - `get_claimable_chunk(env, issuer, namespace, token, holder, start_idx, count)` — computes claimable amount over a bounded index window and returns a `next_cursor` when further eligible periods exist. + These helpers enforce reasonable caps (`MAX_PAGE_LIMIT`, `MAX_CHUNK_PERIODS`) so off-chain orchestrators should iterate using the returned cursors until exhaustion. - **Ordering:** `get_offerings_page` returns offerings by registration index. `get_blacklist` returns addresses in insertion order. `get_pending_periods` returns period IDs by deposit index. All query results are deterministic. - **Minimum revenue threshold:** Issuers can set `set_min_revenue_threshold(issuer, token, min_amount)`. When `report_revenue` is called with `amount < min_amount`, the contract emits `rev_below` and does not update revenue reports or audit summary (skipped distribution). Set to 0 to disable. - **Off-chain:** Prefer small page sizes and bounded blacklist sizes for predictable gas. See storage/gas tests in `src/test.rs` for stress behavior. diff --git a/src/chunking_tests.rs b/src/chunking_tests.rs new file mode 100644 index 00000000..d14819f7 --- /dev/null +++ b/src/chunking_tests.rs @@ -0,0 +1,167 @@ +#![allow(dead_code, unused_variables, unused_imports)] + +use crate::{RevoraRevenueShare, RevoraRevenueShareClient}; +use soroban_sdk::{symbol_short, testutils::Address as _, token, Address, Env, Vec}; + +// Minimal helpers duplicated from src/test.rs so these chunking tests can live separately. +fn make_client(env: &Env) -> RevoraRevenueShareClient<'_> { + let id = env.register_contract(None, RevoraRevenueShare); + RevoraRevenueShareClient::new(env, &id) +} + +fn setup() -> (Env, RevoraRevenueShareClient<'static>, Address) { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let issuer = Address::generate(&env); + (env, client, issuer) +} + +fn create_payment_token(env: &Env) -> (Address, Address) { + let admin = Address::generate(env); + let token_id = env.register_stellar_asset_contract(admin.clone()); + (token_id, admin) +} + +fn mint_tokens(env: &Env, payment_token: &Address, recipient: &Address, amount: &i128) { + token::StellarAssetClient::new(env, payment_token).mint(recipient, amount); +} + +fn setup_with_offering( +) -> (Env, RevoraRevenueShareClient<'static>, Address, Address, Address, Address) { + let (env, client, issuer) = setup(); + let token = Address::generate(&env); + let (payment_token, pt_admin) = create_payment_token(&env); + // Register offering and fund issuer so deposit_revenue can transfer tokens + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payment_token, &0); + mint_tokens(&env, &payment_token, &issuer, &100_000i128); + (env, client, issuer, token, payment_token, pt_admin) +} + +#[test] +fn get_revenue_range_chunk_matches_full_sum() { + let env = Env::default(); + env.mock_all_auths(); + + let client = make_client(&env); + + let issuer = Address::generate(&env); + let token = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1000u32, &token, &0i128); + + // Report revenue for periods 1..=10 + for p in 1u64..=10u64 { + client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &100i128, &p, &false); + } + + // Full sum + let full = client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1u64, &10u64); + + // Sum in chunks of 3 + let mut cursor = 1u64; + let mut acc: i128 = 0; + loop { + let (partial, next) = client.get_revenue_range_chunk( + &issuer, + &symbol_short!("def"), + &token, + &cursor, + &10u64, + &3u32, + ); + acc += partial; + if let Some(n) = next { + cursor = n; + } else { + break; + } + } + + assert_eq!(full, acc); +} + +#[test] +fn pending_periods_page_and_claimable_chunk_consistent() { + let env = Env::default(); + env.mock_all_auths(); + + let client = make_client(&env); + + let issuer = Address::generate(&env); + let token = Address::generate(&env); + let holder = Address::generate(&env); + + let (payment_token, _pt_admin) = create_payment_token(&env); + client.register_offering( + &issuer, + &symbol_short!("def"), + &token, + &1000u32, + &payment_token, + &0i128, + ); + // Mint to issuer so deposit_revenue token transfer succeeds + mint_tokens(&env, &payment_token, &issuer, &100_000i128); + + // Insert periods 1..=8 via the test helper (avoids token transfers in tests) + for p in 1u64..=8u64 { + client.test_insert_period(&issuer, &symbol_short!("def"), &token, &p, &1000i128); + } + + // Set holder share + let r = client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &1000u32); + assert!(r.is_ok()); + + // get_pending_periods full + let full = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder); + + // Page through with limit 3 + let mut cursor = 0u32; + let mut all = Vec::new(&env); + loop { + let (page, next) = client.get_pending_periods_page( + &issuer, + &symbol_short!("def"), + &token, + &holder, + &cursor, + &3u32, + ); + for i in 0..page.len() { + all.push_back(page.get(i).unwrap()); + } + if let Some(n) = next { + cursor = n; + } else { + break; + } + } + + // Compare lengths + assert_eq!(full.len(), all.len()); + + // Now check claimable chunk matches full + let full_claim = client.get_claimable(&issuer, &symbol_short!("def"), &token, &holder); + + // Sum claimable in chunks from index 0, count 2 + let mut idx = 0u32; + let mut acc: i128 = 0; + loop { + let (partial, next) = client.get_claimable_chunk( + &issuer, + &symbol_short!("def"), + &token, + &holder, + &idx, + &2u32, + ); + acc += partial; + if let Some(n) = next { + idx = n; + } else { + break; + } + } + assert_eq!(full_claim, acc); +} diff --git a/src/lib.rs b/src/lib.rs index 4cc58966..c43bac30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,10 +74,10 @@ pub enum RevoraError { // ── Event symbols ──────────────────────────────────────────── const EVENT_REVENUE_REPORTED: Symbol = symbol_short!("rev_rep"); -const EVENT_BL_ADD: Symbol = symbol_short!("bl_add"); -const EVENT_BL_REM: Symbol = symbol_short!("bl_rem"); -const EVENT_WL_ADD: Symbol = symbol_short!("wl_add"); -const EVENT_WL_REM: Symbol = symbol_short!("wl_rem"); +const EVENT_BL_ADD: Symbol = symbol_short!("bl_add"); +const EVENT_BL_REM: Symbol = symbol_short!("bl_rem"); +const EVENT_WL_ADD: Symbol = symbol_short!("wl_add"); +const EVENT_WL_REM: Symbol = symbol_short!("wl_rem"); // ── Storage key ────────────────────────────────────────────── /// One blacklist map per offering, keyed by the offering's token address. @@ -138,7 +138,6 @@ pub struct Proposal { pub executed: bool, } - const EVENT_SNAP_CONFIG: Symbol = symbol_short!("snap_cfg"); const EVENT_INIT: Symbol = symbol_short!("init"); @@ -156,6 +155,7 @@ const EVENT_METADATA_UPDATED: Symbol = symbol_short!("meta_upd"); /// Emitted when per-offering minimum revenue threshold is set or changed (#25). const EVENT_MIN_REV_THRESHOLD_SET: Symbol = symbol_short!("min_rev"); /// Emitted when reported revenue is below the offering's minimum threshold; no distribution triggered (#25). +#[allow(dead_code)] const EVENT_REV_BELOW_THRESHOLD: Symbol = symbol_short!("rev_below"); /// Emitted when an offering's supply cap is reached (#96). const EVENT_SUPPLY_CAP_REACHED: Symbol = symbol_short!("cap_reach"); @@ -477,6 +477,10 @@ const MAX_PLATFORM_FEE_BPS: u32 = 5_000; /// Keeps compute costs predictable within Soroban limits. const MAX_CLAIM_PERIODS: u32 = 50; +/// Maximum number of periods allowed in a single read-only chunked query. +/// This is a safety cap to prevent accidental long-running loops in read-only methods. +const MAX_CHUNK_PERIODS: u32 = 200; + // ── Contract ───────────────────────────────────────────────── #[contract] pub struct RevoraRevenueShare; @@ -487,10 +491,7 @@ impl RevoraRevenueShare { fn is_event_versioning_enabled(env: Env) -> bool { let key = DataKey::EventVersioningEnabled; - env.storage() - .persistent() - .get::(&key) - .unwrap_or(false) + env.storage().persistent().get::(&key).unwrap_or(false) } /// Returns error if contract is frozen (#32). Call at start of state-mutating entrypoints. @@ -702,21 +703,16 @@ impl RevoraRevenueShare { /// Return the supply cap for an offering (0 = no cap). (#96) pub fn get_supply_cap(env: Env, issuer: Address, namespace: Symbol, token: Address) -> i128 { let offering_id = OfferingId { issuer, namespace, token }; - env.storage() - .persistent() - .get(&DataKey::SupplyCap(offering_id)) - .unwrap_or(0) + env.storage().persistent().get(&DataKey::SupplyCap(offering_id)).unwrap_or(0) } /// Return true if the contract is in event-only mode. pub fn is_event_only(env: &Env) -> bool { - env.storage() - .persistent() - .get::(&DataKey::EventOnlyMode) - .unwrap_or(false) + env.storage().persistent().get::(&DataKey::EventOnlyMode).unwrap_or(false) } /// Input validation (#35): require amount > 0 for transfers/deposits. + #[allow(dead_code)] fn require_positive_amount(amount: i128) -> Result<(), RevoraError> { if amount <= 0 { return Err(RevoraError::InvalidAmount); @@ -725,6 +721,7 @@ impl RevoraRevenueShare { } /// Input validation (#35): require period_id > 0 where 0 would be ambiguous. + #[allow(dead_code)] fn require_valid_period_id(period_id: u64) -> Result<(), RevoraError> { if period_id == 0 { return Err(RevoraError::InvalidPeriodId); @@ -740,7 +737,6 @@ impl RevoraRevenueShare { Ok(()) } - /// Initialize the contract with an admin and an optional safety role. /// /// This method follows the singleton pattern and can only be called once. @@ -752,7 +748,12 @@ impl RevoraRevenueShare { /// ### Panics /// Panics if the contract has already been initialized. /// Get the current issuer for an offering token (used for auth checks after transfers). - fn get_current_issuer(env: &Env, issuer: Address, namespace: Symbol, token: Address) -> Option
{ + fn get_current_issuer( + env: &Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option
{ let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::OfferingIssuer(offering_id); env.storage().persistent().get(&key) @@ -857,7 +858,6 @@ impl RevoraRevenueShare { if env.storage().persistent().get::(&DataKey::Paused).unwrap_or(false) { panic!("contract is paused"); } - } // ── Offering management ─────────────────────────────────── @@ -904,18 +904,11 @@ impl RevoraRevenueShare { let registered_key = DataKey::IssuerRegistered(issuer.clone()); if !env.storage().persistent().has(®istered_key) { let issuer_count_key = DataKey::IssuerCount; - let issuer_count: u32 = env - .storage() - .persistent() - .get(&issuer_count_key) - .unwrap_or(0); + let issuer_count: u32 = + env.storage().persistent().get(&issuer_count_key).unwrap_or(0); let issuer_item_key = DataKey::IssuerItem(issuer_count); - env.storage() - .persistent() - .set(&issuer_item_key, &issuer.clone()); - env.storage() - .persistent() - .set(&issuer_count_key, &(issuer_count + 1)); + env.storage().persistent().set(&issuer_item_key, &issuer.clone()); + env.storage().persistent().set(&issuer_count_key, &(issuer_count + 1)); env.storage().persistent().set(®istered_key, &true); } @@ -924,15 +917,14 @@ impl RevoraRevenueShare { if !env.storage().persistent().has(&ns_reg_key) { let ns_count_key = DataKey::NamespaceCount(issuer.clone()); let count: u32 = env.storage().persistent().get(&ns_count_key).unwrap_or(0); - env.storage().persistent().set(&DataKey::NamespaceItem(issuer.clone(), count), &namespace); + env.storage() + .persistent() + .set(&DataKey::NamespaceItem(issuer.clone(), count), &namespace); env.storage().persistent().set(&ns_count_key, &(count + 1)); env.storage().persistent().set(&ns_reg_key, &true); } - let tenant_id = TenantId { - issuer: issuer.clone(), - namespace: namespace.clone(), - }; + let tenant_id = TenantId { issuer: issuer.clone(), namespace: namespace.clone() }; let count_key = DataKey::OfferCount(tenant_id.clone()); let count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0); @@ -986,12 +978,7 @@ impl RevoraRevenueShare { if Self::is_event_versioning_enabled(env.clone()) { env.events().publish( (EVENT_OFFER_REG_V1, issuer.clone(), namespace.clone()), - ( - EVENT_SCHEMA_VERSION, - token.clone(), - revenue_share_bps, - payout_asset.clone(), - ), + (EVENT_SCHEMA_VERSION, token.clone(), revenue_share_bps, payout_asset.clone()), ); } Ok(()) @@ -1020,7 +1007,12 @@ impl RevoraRevenueShare { /// ### Returns /// - `Some(Offering)` if found. /// - `None` otherwise. - pub fn get_offering(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Option { + pub fn get_offering( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option { let count = Self::get_offering_count(env.clone(), issuer.clone(), namespace.clone()); let tenant_id = TenantId { issuer, namespace }; for i in 0..count { @@ -1035,7 +1027,8 @@ impl RevoraRevenueShare { /// List all offering tokens for an issuer in a namespace. pub fn list_offerings(env: Env, issuer: Address, namespace: Symbol) -> Vec
{ - let (page, _) = Self::get_offerings_page(env.clone(), issuer.clone(), namespace, 0, MAX_PAGE_LIMIT); + let (page, _) = + Self::get_offerings_page(env.clone(), issuer.clone(), namespace, 0, MAX_PAGE_LIMIT); let mut tokens = Vec::new(&env); for i in 0..page.len() { tokens.push_back(page.get(i).unwrap().token); @@ -1043,7 +1036,6 @@ impl RevoraRevenueShare { tokens } - /// Record a revenue report for an offering; updates audit summary and emits events. #[allow(clippy::too_many_arguments)] pub fn report_revenue( @@ -1078,13 +1070,9 @@ impl RevoraRevenueShare { return Err(RevoraError::OfferingNotFound); } - let offering = Self::get_offering( - env.clone(), - issuer.clone(), - namespace.clone(), - token.clone(), - ) - .ok_or(RevoraError::OfferingNotFound)?; + let offering = + Self::get_offering(env.clone(), issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if offering.payout_asset != payout_asset { return Err(RevoraError::PayoutAssetMismatch); } @@ -1094,10 +1082,8 @@ impl RevoraRevenueShare { if !testnet_mode { // Holder concentration guardrail (#26): reject if enforce and over limit let limit_key = DataKey::ConcentrationLimit(offering_id.clone()); - if let Some(config) = env - .storage() - .persistent() - .get::(&limit_key) + if let Some(config) = + env.storage().persistent().get::(&limit_key) { if config.enforce && config.max_bps > 0 { let curr_key = DataKey::CurrentConcentration(offering_id.clone()); @@ -1118,11 +1104,8 @@ impl RevoraRevenueShare { if !event_only { let key = DataKey::RevenueReports(offering_id.clone()); - let mut reports: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let mut reports: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); let current_timestamp = env.ledger().timestamp(); let idx_key = DataKey::RevenueIndex(offering_id.clone(), period_id); let mut cumulative_revenue: i128 = @@ -1135,7 +1118,12 @@ impl RevoraRevenueShare { env.storage().persistent().set(&key, &reports); env.events().publish( - (EVENT_REVENUE_REPORT_OVERRIDE, issuer.clone(), namespace.clone(), token.clone()), + ( + EVENT_REVENUE_REPORT_OVERRIDE, + issuer.clone(), + namespace.clone(), + token.clone(), + ), (amount, period_id, existing_amount, blacklist.clone()), ); env.events().publish( @@ -1160,11 +1148,22 @@ impl RevoraRevenueShare { namespace.clone(), token.clone(), ), - (payout_asset.clone(), amount, period_id, existing_amount, blacklist.clone()), + ( + payout_asset.clone(), + amount, + period_id, + existing_amount, + blacklist.clone(), + ), ); } else { env.events().publish( - (EVENT_REVENUE_REPORT_REJECTED, issuer.clone(), namespace.clone(), token.clone()), + ( + EVENT_REVENUE_REPORT_REJECTED, + issuer.clone(), + namespace.clone(), + token.clone(), + ), (amount, period_id, existing_amount, blacklist.clone()), ); env.events().publish( @@ -1189,22 +1188,31 @@ impl RevoraRevenueShare { namespace.clone(), token.clone(), ), - (payout_asset.clone(), amount, period_id, existing_amount, blacklist.clone()), + ( + payout_asset.clone(), + amount, + period_id, + existing_amount, + blacklist.clone(), + ), ); } } None => { // Initial report for this period cumulative_revenue = cumulative_revenue.checked_add(amount).unwrap_or(amount); - env.storage() - .persistent() - .set(&idx_key, &cumulative_revenue); + env.storage().persistent().set(&idx_key, &cumulative_revenue); reports.set(period_id, (amount, current_timestamp)); env.storage().persistent().set(&key, &reports); env.events().publish( - (EVENT_REVENUE_REPORT_INITIAL, issuer.clone(), namespace.clone(), token.clone()), + ( + EVENT_REVENUE_REPORT_INITIAL, + issuer.clone(), + namespace.clone(), + token.clone(), + ), (amount, period_id, blacklist.clone()), ); env.events().publish( @@ -1260,12 +1268,7 @@ impl RevoraRevenueShare { ); env.events().publish( - ( - EVENT_REVENUE_REPORTED_ASSET, - issuer.clone(), - namespace.clone(), - token.clone(), - ), + (EVENT_REVENUE_REPORTED_ASSET, issuer.clone(), namespace.clone(), token.clone()), (payout_asset.clone(), amount, period_id), ); @@ -1287,12 +1290,7 @@ impl RevoraRevenueShare { ); env.events().publish( - ( - EVENT_REV_INIA_V1, - issuer.clone(), - namespace.clone(), - token.clone(), - ), + (EVENT_REV_INIA_V1, issuer.clone(), namespace.clone(), token.clone()), (EVENT_SCHEMA_VERSION, payout_asset.clone(), amount, period_id, blacklist.clone()), ); @@ -1302,12 +1300,7 @@ impl RevoraRevenueShare { ); env.events().publish( - ( - EVENT_REV_REPA_V1, - issuer.clone(), - namespace.clone(), - token.clone(), - ), + (EVENT_REV_REPA_V1, issuer.clone(), namespace.clone(), token.clone()), (EVENT_SCHEMA_VERSION, payout_asset.clone(), amount, period_id), ); } @@ -1315,14 +1308,11 @@ impl RevoraRevenueShare { if !event_only { // Audit log summary (#34): maintain per-offering total revenue and report count let summary_key = DataKey::AuditSummary(offering_id); - let mut summary: AuditSummary = - env.storage() - .persistent() - .get(&summary_key) - .unwrap_or(AuditSummary { - total_revenue: 0, - report_count: 0, - }); + let mut summary: AuditSummary = env + .storage() + .persistent() + .get(&summary_key) + .unwrap_or(AuditSummary { total_revenue: 0, report_count: 0 }); summary.total_revenue = summary.total_revenue.saturating_add(amount); summary.report_count = summary.report_count.saturating_add(1); env.storage().persistent().set(&summary_key, &summary); @@ -1331,19 +1321,77 @@ impl RevoraRevenueShare { Ok(()) } - pub fn get_revenue_by_period(env: Env, issuer: Address, namespace: Symbol, token: Address, period_id: u64) -> i128 { + pub fn get_revenue_by_period( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + period_id: u64, + ) -> i128 { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::RevenueIndex(offering_id, period_id); env.storage().persistent().get(&key).unwrap_or(0) } - pub fn get_revenue_range(env: Env, issuer: Address, namespace: Symbol, token: Address, from_period: u64, to_period: u64) -> i128 { + pub fn get_revenue_range( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + from_period: u64, + to_period: u64, + ) -> i128 { let mut total: i128 = 0; for period in from_period..=to_period { - total += Self::get_revenue_by_period(env.clone(), issuer.clone(), namespace.clone(), token.clone(), period); + total += Self::get_revenue_by_period( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + period, + ); } total } + + /// Read-only: sum revenue for a numeric period range but bounded by `max_periods` per call. + /// Returns `(sum, next_start)` where `next_start` is `Some(period)` if there are remaining + /// periods to process and a subsequent call can continue from that period. `max_periods` of 0 + /// or > `MAX_CHUNK_PERIODS` will be capped to `MAX_CHUNK_PERIODS` to enforce limits. + pub fn get_revenue_range_chunk( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + from_period: u64, + to_period: u64, + max_periods: u32, + ) -> (i128, Option) { + let mut total: i128 = 0; + let mut processed: u32 = 0; + let cap = if max_periods == 0 || max_periods > MAX_CHUNK_PERIODS { + MAX_CHUNK_PERIODS + } else { + max_periods + }; + + let mut p = from_period; + while p <= to_period { + if processed >= cap { + return (total, Some(p)); + } + total += Self::get_revenue_by_period( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + p, + ); + processed = processed.saturating_add(1); + p = p.saturating_add(1); + } + (total, None) + } /// Return the total number of offerings registered by `issuer` in `namespace`. pub fn get_offering_count(env: Env, issuer: Address, namespace: Symbol) -> u32 { let tenant_id = TenantId { issuer, namespace }; @@ -1410,15 +1458,16 @@ impl RevoraRevenueShare { Self::require_not_paused(&env); caller.require_auth(); - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; if !Self::is_event_only(&env) { let key = DataKey::Blacklist(offering_id.clone()); - let mut map: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let mut map: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); map.set(investor.clone(), true); env.storage().persistent().set(&key, &map); @@ -1444,19 +1493,13 @@ impl RevoraRevenueShare { // Maintain insertion order for deterministic get_blacklist (#38) if !was_present { let order_key = DataKey::BlacklistOrder(offering_id.clone()); - let mut order: Vec
= env - .storage() - .persistent() - .get(&order_key) - .unwrap_or_else(|| Vec::new(&env)); + let mut order: Vec
= + env.storage().persistent().get(&order_key).unwrap_or_else(|| Vec::new(&env)); order.push_back(investor.clone()); env.storage().persistent().set(&order_key, &order); } - env.events().publish( - (EVENT_BL_ADD, issuer, namespace, token), - (caller, investor), - ); + env.events().publish((EVENT_BL_ADD, issuer, namespace, token), (caller, investor)); Ok(()) } @@ -1485,7 +1528,11 @@ impl RevoraRevenueShare { Self::require_not_paused(&env); caller.require_auth(); - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) @@ -1497,21 +1544,15 @@ impl RevoraRevenueShare { if !Self::is_event_only(&env) { let key = DataKey::Blacklist(offering_id.clone()); - let mut map: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let mut map: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); map.remove(investor.clone()); env.storage().persistent().set(&key, &map); // Rebuild order vec so get_blacklist stays deterministic (#38) let order_key = DataKey::BlacklistOrder(offering_id.clone()); - let old_order: Vec
= env - .storage() - .persistent() - .get(&order_key) - .unwrap_or_else(|| Vec::new(&env)); + let old_order: Vec
= + env.storage().persistent().get(&order_key).unwrap_or_else(|| Vec::new(&env)); let mut new_order = Vec::new(&env); for i in 0..old_order.len() { let addr = old_order.get(i).unwrap(); @@ -1522,15 +1563,18 @@ impl RevoraRevenueShare { env.storage().persistent().set(&order_key, &new_order); } - env.events().publish( - (EVENT_BL_REM, issuer, namespace, token), - (caller, investor), - ); + env.events().publish((EVENT_BL_REM, issuer, namespace, token), (caller, investor)); Ok(()) } /// Returns `true` if `investor` is blacklisted for an offering. - pub fn is_blacklisted(env: Env, issuer: Address, namespace: Symbol, token: Address, investor: Address) -> bool { + pub fn is_blacklisted( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + investor: Address, + ) -> bool { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Blacklist(offering_id); env.storage() @@ -1542,7 +1586,12 @@ impl RevoraRevenueShare { /// Return all blacklisted addresses for an offering. /// Ordering: by insertion order, deterministic and stable across calls (#38). - pub fn get_blacklist(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Vec
{ + pub fn get_blacklist( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Vec
{ let offering_id = OfferingId { issuer, namespace, token }; let order_key = DataKey::BlacklistOrder(offering_id); env.storage() @@ -1561,27 +1610,37 @@ impl RevoraRevenueShare { /// When a whitelist exists (non-empty), only whitelisted addresses /// are eligible for revenue distribution (subject to blacklist override). /// Add `investor` to the per-offering whitelist. - pub fn whitelist_add(env: Env, caller: Address, issuer: Address, namespace: Symbol, token: Address, investor: Address) { + pub fn whitelist_add( + env: Env, + caller: Address, + issuer: Address, + namespace: Symbol, + token: Address, + investor: Address, + ) { caller.require_auth(); - let current_issuer = Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) - .expect("offering not found"); + let current_issuer = + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .expect("offering not found"); if caller != current_issuer { panic!("not authorized"); } let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Whitelist(offering_id.clone()); - let mut map: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let mut map: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); map.set(investor.clone(), true); env.storage().persistent().set(&key, &map); env.events().publish( - (EVENT_WL_ADD, offering_id.issuer.clone(), offering_id.namespace.clone(), offering_id.token.clone()), + ( + EVENT_WL_ADD, + offering_id.issuer.clone(), + offering_id.namespace.clone(), + offering_id.token.clone(), + ), (caller, investor), ); } @@ -1590,27 +1649,37 @@ impl RevoraRevenueShare { /// /// Idempotent — calling when the address is not listed is safe. /// Remove `investor` from the per-offering whitelist. - pub fn whitelist_remove(env: Env, caller: Address, issuer: Address, namespace: Symbol, token: Address, investor: Address) { + pub fn whitelist_remove( + env: Env, + caller: Address, + issuer: Address, + namespace: Symbol, + token: Address, + investor: Address, + ) { caller.require_auth(); - let current_issuer = Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) - .expect("offering not found"); + let current_issuer = + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .expect("offering not found"); if caller != current_issuer { panic!("not authorized"); } let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Whitelist(offering_id.clone()); - let mut map: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let mut map: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); map.remove(investor.clone()); env.storage().persistent().set(&key, &map); env.events().publish( - (EVENT_WL_REM, offering_id.issuer.clone(), offering_id.namespace.clone(), offering_id.token.clone()), + ( + EVENT_WL_REM, + offering_id.issuer.clone(), + offering_id.namespace.clone(), + offering_id.token.clone(), + ), (caller, investor), ); } @@ -1619,7 +1688,13 @@ impl RevoraRevenueShare { /// /// Note: If the whitelist is empty (disabled), this returns `false`. /// Use `is_whitelist_enabled` to check if whitelist enforcement is active. - pub fn is_whitelisted(env: Env, issuer: Address, namespace: Symbol, token: Address, investor: Address) -> bool { + pub fn is_whitelisted( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + investor: Address, + ) -> bool { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Whitelist(offering_id); env.storage() @@ -1630,7 +1705,12 @@ impl RevoraRevenueShare { } /// Return all whitelisted addresses for an offering. - pub fn get_whitelist(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Vec
{ + pub fn get_whitelist( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Vec
{ let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Whitelist(offering_id); env.storage() @@ -1641,14 +1721,16 @@ impl RevoraRevenueShare { } /// Returns `true` if whitelist enforcement is enabled for an offering. - pub fn is_whitelist_enabled(env: Env, issuer: Address, namespace: Symbol, token: Address) -> bool { + pub fn is_whitelist_enabled( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> bool { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::Whitelist(offering_id); - let map: Map = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| Map::new(&env)); + let map: Map = + env.storage().persistent().get(&key).unwrap_or_else(|| Map::new(&env)); !map.is_empty() } @@ -1680,9 +1762,14 @@ impl RevoraRevenueShare { Self::require_not_frozen(&env)?; // Verify offering exists and issuer is current - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::LimitReached)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::LimitReached)?; if current_issuer != issuer { return Err(RevoraError::LimitReached); @@ -1691,9 +1778,7 @@ impl RevoraRevenueShare { if !Self::is_event_only(&env) { issuer.require_auth(); let key = DataKey::ConcentrationLimit(offering_id); - env.storage() - .persistent() - .set(&key, &ConcentrationLimitConfig { max_bps, enforce }); + env.storage().persistent().set(&key, &ConcentrationLimitConfig { max_bps, enforce }); } Ok(()) } @@ -1720,11 +1805,16 @@ impl RevoraRevenueShare { ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; issuer.require_auth(); - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; // Verify offering exists and issuer is current let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); @@ -1732,9 +1822,7 @@ impl RevoraRevenueShare { if !Self::is_event_only(&env) { let curr_key = DataKey::CurrentConcentration(offering_id.clone()); - env.storage() - .persistent() - .set(&curr_key, &concentration_bps); + env.storage().persistent().set(&curr_key, &concentration_bps); } let limit_key = DataKey::ConcentrationLimit(offering_id); @@ -1764,7 +1852,12 @@ impl RevoraRevenueShare { } /// Get last reported concentration in bps for an offering. - pub fn get_current_concentration(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Option { + pub fn get_current_concentration( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::CurrentConcentration(offering_id); env.storage().persistent().get(&key) @@ -1773,7 +1866,12 @@ impl RevoraRevenueShare { // ── Audit log summary (#34) ──────────────────────────────── /// Get per-offering audit summary (total revenue and report count). - pub fn get_audit_summary(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Option { + pub fn get_audit_summary( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::AuditSummary(offering_id); env.storage().persistent().get(&key) @@ -1788,10 +1886,15 @@ impl RevoraRevenueShare { mode: RoundingMode, ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; - + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; + if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); } @@ -1802,13 +1905,15 @@ impl RevoraRevenueShare { } /// Get rounding mode for an offering. - pub fn get_rounding_mode(env: Env, issuer: Address, namespace: Symbol, token: Address) -> RoundingMode { + pub fn get_rounding_mode( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> RoundingMode { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::RoundingMode(offering_id); - env.storage() - .persistent() - .get(&key) - .unwrap_or(RoundingMode::Truncation) + env.storage().persistent().get(&key).unwrap_or(RoundingMode::Truncation) } // ── Per-offering investment constraints (#97) ───────────── @@ -1823,9 +1928,14 @@ impl RevoraRevenueShare { max_stake: i128, ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); } @@ -1838,9 +1948,7 @@ impl RevoraRevenueShare { } let key = DataKey::InvestmentConstraints(offering_id); let previous = env.storage().persistent().get::(&key); - env.storage() - .persistent() - .set(&key, &InvestmentConstraintsConfig { min_stake, max_stake }); + env.storage().persistent().set(&key, &InvestmentConstraintsConfig { min_stake, max_stake }); env.events().publish( (EVENT_INV_CONSTRAINTS, issuer, namespace, token), (min_stake, max_stake, previous.is_some()), @@ -1874,9 +1982,14 @@ impl RevoraRevenueShare { ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); @@ -1898,7 +2011,12 @@ impl RevoraRevenueShare { } /// Get minimum revenue threshold for an offering. 0 means no threshold. - pub fn get_min_revenue_threshold(env: Env, issuer: Address, namespace: Symbol, token: Address) -> i128 { + pub fn get_min_revenue_threshold( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> i128 { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::MinRevenueThreshold(offering_id); env.storage().persistent().get(&key).unwrap_or(0) @@ -1966,8 +2084,9 @@ impl RevoraRevenueShare { // Verify offering exists and issuer is current let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; - + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; + if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); } @@ -1991,11 +2110,16 @@ impl RevoraRevenueShare { issuer.require_auth(); // 1. Verify snapshots are enabled - if !Self::get_snapshot_config(env.clone(), issuer.clone(), namespace.clone(), token.clone()) { + if !Self::get_snapshot_config(env.clone(), issuer.clone(), namespace.clone(), token.clone()) + { return Err(RevoraError::SnapshotNotEnabled); } - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; // 2. Validate snapshot reference (must be strictly monotonic) let snap_key = DataKey::LastSnapshotRef(offering_id.clone()); @@ -2016,9 +2140,7 @@ impl RevoraRevenueShare { )?; // 4. Update last snapshot and emit specialized event - env.storage() - .persistent() - .set(&snap_key, &snapshot_reference); + env.storage().persistent().set(&snap_key, &snapshot_reference); env.events().publish( (EVENT_REV_DEP_SNAP, issuer, namespace, token), (payment_token, amount, period_id, snapshot_reference), @@ -2037,26 +2159,40 @@ impl RevoraRevenueShare { ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; issuer.require_auth(); - if Self::get_offering(env.clone(), issuer.clone(), namespace.clone(), token.clone()).is_none() { + if Self::get_offering(env.clone(), issuer.clone(), namespace.clone(), token.clone()) + .is_none() + { return Err(RevoraError::OfferingNotFound); } let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::SnapshotConfig(offering_id.clone()); env.storage().persistent().set(&key, &enabled); - env.events() - .publish((EVENT_SNAP_CONFIG, offering_id.issuer, offering_id.namespace, offering_id.token), enabled); + env.events().publish( + (EVENT_SNAP_CONFIG, offering_id.issuer, offering_id.namespace, offering_id.token), + enabled, + ); Ok(()) } /// Check if snapshot-based distribution is enabled for an offering. - pub fn get_snapshot_config(env: Env, issuer: Address, namespace: Symbol, token: Address) -> bool { + pub fn get_snapshot_config( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> bool { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::SnapshotConfig(offering_id); env.storage().persistent().get(&key).unwrap_or(false) } /// Get the latest recorded snapshot reference for an offering. - pub fn get_last_snapshot_ref(env: Env, issuer: Address, namespace: Symbol, token: Address) -> u64 { + pub fn get_last_snapshot_ref( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> u64 { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::LastSnapshotRef(offering_id); env.storage().persistent().get(&key).unwrap_or(0) @@ -2089,9 +2225,14 @@ impl RevoraRevenueShare { Self::require_not_frozen(&env)?; // Verify offering exists and issuer is current - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); @@ -2293,7 +2434,13 @@ impl RevoraRevenueShare { } /// Return a holder's share in basis points for an offering (0 if unset). - pub fn get_holder_share(env: Env, issuer: Address, namespace: Symbol, token: Address, holder: Address) -> u32 { + pub fn get_holder_share( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + holder: Address, + ) -> u32 { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::HolderShare(offering_id, holder); env.storage().persistent().get(&key).unwrap_or(0) @@ -2324,11 +2471,23 @@ impl RevoraRevenueShare { ) -> Result { holder.require_auth(); - if Self::is_blacklisted(env.clone(), issuer.clone(), namespace.clone(), token.clone(), holder.clone()) { + if Self::is_blacklisted( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + holder.clone(), + ) { return Err(RevoraError::HolderBlacklisted); } - let share_bps = Self::get_holder_share(env.clone(), issuer.clone(), namespace.clone(), token.clone(), holder.clone()); + let share_bps = Self::get_holder_share( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + holder.clone(), + ); if share_bps == 0 { return Err(RevoraError::NoPendingClaims); } @@ -2543,7 +2702,13 @@ impl RevoraRevenueShare { /// Return unclaimed period IDs for a holder on an offering. /// Ordering: by deposit index (creation order), deterministic (#38). - pub fn get_pending_periods(env: Env, issuer: Address, namespace: Symbol, token: Address, holder: Address) -> Vec { + pub fn get_pending_periods( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + holder: Address, + ) -> Vec { let offering_id = OfferingId { issuer, namespace, token }; let count_key = DataKey::PeriodCount(offering_id.clone()); let period_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0); @@ -2554,17 +2719,76 @@ impl RevoraRevenueShare { let mut periods = Vec::new(&env); for i in start_idx..period_count { let entry_key = DataKey::PeriodEntry(offering_id.clone(), i); - let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap(); + let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap_or(0); + if period_id == 0 { + continue; + } periods.push_back(period_id); } periods } + /// Read-only: return a page of pending period IDs for a holder, bounded by `limit`. + /// Returns `(periods_page, next_cursor)` where `next_cursor` is `Some(next_index)` when more + /// periods remain, otherwise `None`. `limit` of 0 or greater than `MAX_PAGE_LIMIT` will be + /// capped to `MAX_PAGE_LIMIT` to keep calls predictable. + pub fn get_pending_periods_page( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + holder: Address, + start: u32, + limit: u32, + ) -> (Vec, Option) { + let offering_id = OfferingId { issuer, namespace, token }; + let count_key = DataKey::PeriodCount(offering_id.clone()); + let period_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0); + + let idx_key = DataKey::LastClaimedIdx(offering_id.clone(), holder); + let holder_start_idx: u32 = env.storage().persistent().get(&idx_key).unwrap_or(0); + + let actual_start = core::cmp::max(start, holder_start_idx); + + if actual_start >= period_count { + return (Vec::new(&env), None); + } + + let effective_limit = + if limit == 0 || limit > MAX_PAGE_LIMIT { MAX_PAGE_LIMIT } else { limit }; + let end = core::cmp::min(actual_start + effective_limit, period_count); + + let mut results = Vec::new(&env); + for i in actual_start..end { + let entry_key = DataKey::PeriodEntry(offering_id.clone(), i); + let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap_or(0); + if period_id == 0 { + continue; + } + results.push_back(period_id); + } + + let next_cursor = if end < period_count { Some(end) } else { None }; + (results, next_cursor) + } + /// Preview the total claimable amount for a holder without mutating state. /// /// This method respects the per-offering claim delay and only sums periods that have passed the delay. - pub fn get_claimable(env: Env, issuer: Address, namespace: Symbol, token: Address, holder: Address) -> i128 { - let share_bps = Self::get_holder_share(env.clone(), issuer.clone(), namespace.clone(), token.clone(), holder.clone()); + pub fn get_claimable( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + holder: Address, + ) -> i128 { + let share_bps = Self::get_holder_share( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + holder.clone(), + ); if share_bps == 0 { return 0; } @@ -2584,19 +2808,90 @@ impl RevoraRevenueShare { let mut total: i128 = 0; for i in start_idx..period_count { let entry_key = DataKey::PeriodEntry(offering_id.clone(), i); - let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap(); + let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap_or(0); + if period_id == 0 { + continue; + } let time_key = DataKey::PeriodDepositTime(offering_id.clone(), period_id); let deposit_time: u64 = env.storage().persistent().get(&time_key).unwrap_or(0); if delay_secs > 0 && now < deposit_time.saturating_add(delay_secs) { break; } let rev_key = DataKey::PeriodRevenue(offering_id.clone(), period_id); - let revenue: i128 = env.storage().persistent().get(&rev_key).unwrap(); + let revenue: i128 = env.storage().persistent().get(&rev_key).unwrap_or(0); total += revenue * (share_bps as i128) / 10_000; } total } + /// Read-only: compute claimable amount for a holder over a bounded index window. + /// Returns `(total, next_cursor)` where `next_cursor` is `Some(next_index)` if more + /// eligible periods exist after the processed window. `count` of 0 or > `MAX_CHUNK_PERIODS` + /// will be capped to `MAX_CHUNK_PERIODS` to enforce limits. + pub fn get_claimable_chunk( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + holder: Address, + start_idx: u32, + count: u32, + ) -> (i128, Option) { + let share_bps = Self::get_holder_share( + env.clone(), + issuer.clone(), + namespace.clone(), + token.clone(), + holder.clone(), + ); + if share_bps == 0 { + return (0, None); + } + + let offering_id = OfferingId { issuer, namespace, token }; + + let count_key = DataKey::PeriodCount(offering_id.clone()); + let period_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0); + + if start_idx >= period_count { + return (0, None); + } + + let cap = if count == 0 || count > MAX_CHUNK_PERIODS { MAX_CHUNK_PERIODS } else { count }; + + let delay_key = DataKey::ClaimDelaySecs(offering_id.clone()); + let delay_secs: u64 = env.storage().persistent().get(&delay_key).unwrap_or(0); + let now = env.ledger().timestamp(); + + let mut total: i128 = 0; + let mut processed: u32 = 0; + let mut idx = start_idx; + + while idx < period_count { + if processed >= cap { + return (total, Some(idx)); + } + let entry_key = DataKey::PeriodEntry(offering_id.clone(), idx); + let period_id: u64 = env.storage().persistent().get(&entry_key).unwrap_or(0); + if period_id == 0 { + idx = idx.saturating_add(1); + continue; + } + let time_key = DataKey::PeriodDepositTime(offering_id.clone(), period_id); + let deposit_time: u64 = env.storage().persistent().get(&time_key).unwrap_or(0); + if delay_secs > 0 && now < deposit_time.saturating_add(delay_secs) { + return (total, Some(idx)); + } + let rev_key = DataKey::PeriodRevenue(offering_id.clone(), period_id); + let revenue: i128 = env.storage().persistent().get(&rev_key).unwrap_or(0); + total += revenue * (share_bps as i128) / 10_000; + processed = processed.saturating_add(1); + idx = idx.saturating_add(1); + } + + (total, None) + } + // ── Time-delayed claim configuration (#27) ────────────────── /// Set the claim delay for an offering in seconds. @@ -2610,9 +2905,14 @@ impl RevoraRevenueShare { Self::require_not_frozen(&env)?; // Verify offering exists and issuer is current - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); @@ -2639,6 +2939,43 @@ impl RevoraRevenueShare { env.storage().persistent().get(&count_key).unwrap_or(0) } + /// Test helper: insert a period entry and revenue without transferring tokens. + /// Only compiled in test builds to avoid affecting production contract. + #[cfg(test)] + pub fn test_insert_period( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + period_id: u64, + amount: i128, + ) { + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; + // Append to indexed period list + let count_key = DataKey::PeriodCount(offering_id.clone()); + let count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0); + let entry_key = DataKey::PeriodEntry(offering_id.clone(), count); + env.storage().persistent().set(&entry_key, &period_id); + env.storage().persistent().set(&count_key, &(count + 1)); + + // Store period revenue and deposit time + let rev_key = DataKey::PeriodRevenue(offering_id.clone(), period_id); + env.storage().persistent().set(&rev_key, &amount); + let time_key = DataKey::PeriodDepositTime(offering_id.clone(), period_id); + let deposit_time = env.ledger().timestamp(); + env.storage().persistent().set(&time_key, &deposit_time); + + // Update cumulative deposited revenue + let deposited_key = DataKey::DepositedRevenue(offering_id.clone()); + let deposited: i128 = env.storage().persistent().get(&deposited_key).unwrap_or(0); + let new_deposited = deposited.saturating_add(amount); + env.storage().persistent().set(&deposited_key, &new_deposited); + } + // ── On-chain distribution simulation (#29) ──────────────────── /// Read-only: simulate distribution for sample inputs without mutating state. @@ -2919,9 +3256,14 @@ impl RevoraRevenueShare { Self::require_not_frozen(&env)?; // Get current issuer and verify offering exists - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; // Only current issuer can propose transfer current_issuer.require_auth(); @@ -2952,26 +3294,29 @@ impl RevoraRevenueShare { ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; // Get pending transfer let pending_key = DataKey::PendingIssuerTransfer(offering_id.clone()); - let new_issuer: Address = env - .storage() - .persistent() - .get(&pending_key) - .ok_or(RevoraError::NoTransferPending)?; + let new_issuer: Address = + env.storage().persistent().get(&pending_key).ok_or(RevoraError::NoTransferPending)?; // Only the proposed new issuer can accept new_issuer.require_auth(); // Get current issuer let old_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; // Update the offering's issuer field in storage - let offering = Self::get_offering(env.clone(), issuer.clone(), namespace.clone(), token.clone()) - .ok_or(RevoraError::OfferingNotFound)?; + let offering = + Self::get_offering(env.clone(), issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; let old_tenant = TenantId { issuer: old_issuer.clone(), namespace: namespace.clone() }; let new_tenant = TenantId { issuer: new_issuer.clone(), namespace: namespace.clone() }; @@ -3008,30 +3353,23 @@ impl RevoraRevenueShare { // Move the last offering to the removed index let last_key = DataKey::OfferItem(old_tenant.clone(), count - 1); let last_offering: Offering = env.storage().persistent().get(&last_key).unwrap(); - env.storage() - .persistent() - .set(&old_item_key, &last_offering); + env.storage().persistent().set(&old_item_key, &last_offering); env.storage().persistent().remove(&last_key); } // Decrement old issuer's count let old_count_key = DataKey::OfferCount(old_tenant.clone()); - env.storage() - .persistent() - .set(&old_count_key, &(count - 1)); + env.storage().persistent().set(&old_count_key, &(count - 1)); // Add to new issuer's storage - let new_count = Self::get_offering_count(env.clone(), new_issuer.clone(), namespace.clone()); + let new_count = + Self::get_offering_count(env.clone(), new_issuer.clone(), namespace.clone()); let new_item_key = DataKey::OfferItem(new_tenant.clone(), new_count); - env.storage() - .persistent() - .set(&new_item_key, &updated_offering); + env.storage().persistent().set(&new_item_key, &updated_offering); // Increment new issuer's count let new_count_key = DataKey::OfferCount(new_tenant.clone()); - env.storage() - .persistent() - .set(&new_count_key, &(new_count + 1)); + env.storage().persistent().set(&new_count_key, &(new_count + 1)); // Update reverse lookup and supply cap keys (they use OfferingId which has issuer) // Wait, does OfferingId change? YES, because issuer is part of OfferingId! @@ -3039,64 +3377,66 @@ impl RevoraRevenueShare { // THIS IS A MAJOR PROBLEM. The data (blacklist, revenue, etc.) is tied to (issuer, namespace, token). // If we transfer the issuer, do we move all the data? // Or do we say OfferingId is (original_issuer, namespace, token)? No, that's not good. - + // Actually, if we transfer issuer, the OfferingId for the new issuer will be different. // We SHOULD probably move all namespaced data or just update the OfferingIssuer mapping. - + // Let's look at DataKey again. OfferingIssuer(OfferingId). // If we want to keep the data, maybe OfferingId should NOT include the issuer? // But the requirement said: "Partition on-chain data based on an issuer identifier (e.g., an address) and a namespace ID (e.g., a symbol)." - - // If issuer A transfers to issuer B, and both are in the SAME namespace, + + // If issuer A transfers to issuer B, and both are in the SAME namespace, // they might want to keep the same token's data. - + // If we use OfferingId { issuer, namespace, token } as key, transferring issuer is basically DELETING the old offering and CREATING a new one. - + // Wait, I should probably use a stable internal ID if I want to support issuer transfers. // But the current implementation uses (issuer, token) as key in many places. - + // If I change (issuer, token) to OfferingId { issuer, namespace, token }, then issuer transfer becomes very expensive (must move all keys). - + // LET'S ASSUME FOR NOW THAT ISSUER TRANSFER UPDATES THE REVERSE LOOKUP and we just deal with the fact that old data is under the old OfferingId. // Actually, that's not good. - + // THE BEST WAY is for the OfferingId to be (namespace, token) ONLY, IF (namespace, token) is unique. // Is (namespace, token) unique across the whole contract? // The requirement says: "Offerings: Partition by namespace." - // An issuer can have multiple namespaces. - // Usually, a token address is unique on-chain. + // An issuer can have multiple namespaces. + // Usually, a token address is unique on-chain. // If multiple issuers try to register the SAME token in DIFFERENT namespaces, is that allowed? // Requirement 1.2: "Enable partitioning of data... Allowing multiple issuers to manage their offerings independently." - + // If Issuer A and Issuer B both register Token T, they should be isolated. // So (Issuer, Namespace, Token) IS the unique identifier. - + // If Issuer A transfers Token T to Issuer B, it's effectively a new (Issuer, Namespace, Token) tuple. - + // For now, I'll follow the logical conclusion: issuer transfer in a multi-tenant system with issuer-based partitioning is basically migrating the data or creating a new partition. - + // But wait, the original code had `OfferingIssuer(token)`. // I changed it to `OfferingIssuer(OfferingId)`. - + // I'll update the OfferingIssuer lookup for the NEW OfferingId but the old data remains under the old OfferingId unless I migrate it. // Migrating data is too expensive in Soroban. - + // Maybe I should RECONSIDER OfferingId. // If OfferingId was (namespace, token), then issuer transfer would just update the `OfferingIssuer` lookup. // But can different issuers use the same (namespace, token)? // Probably not if namespaces are shared. But if namespaces are PRIVATE to issuers? // "Multiple issuers to manage their offerings independently." - + // If Namespace "STOCKS" is used by Issuer A and Issuer B, they should be isolated. // So OfferingId MUST include issuer. - + // Okay, I'll stick with OfferingId including issuer. Issuer transfer will be a "new" offering from the storage perspective. - - let new_offering_id = OfferingId { issuer: new_issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + + let new_offering_id = OfferingId { + issuer: new_issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let issuer_lookup_key = DataKey::OfferingIssuer(new_offering_id); - env.storage() - .persistent() - .set(&issuer_lookup_key, &new_issuer); + env.storage().persistent().set(&issuer_lookup_key, &new_issuer); // Clear pending transfer env.storage().persistent().remove(&pending_key); @@ -3110,12 +3450,18 @@ impl RevoraRevenueShare { } /// Cancel a pending issuer transfer. Only the current issuer may call this. - pub fn cancel_issuer_transfer(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Result<(), RevoraError> { + pub fn cancel_issuer_transfer( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; // Get current issuer let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; // Only current issuer can cancel current_issuer.require_auth(); @@ -3124,17 +3470,19 @@ impl RevoraRevenueShare { // Check if transfer is pending let pending_key = DataKey::PendingIssuerTransfer(offering_id.clone()); - let proposed_new_issuer: Address = env - .storage() - .persistent() - .get(&pending_key) - .ok_or(RevoraError::NoTransferPending)?; + let proposed_new_issuer: Address = + env.storage().persistent().get(&pending_key).ok_or(RevoraError::NoTransferPending)?; // Clear pending transfer env.storage().persistent().remove(&pending_key); env.events().publish( - (EVENT_ISSUER_TRANSFER_CANCELLED, offering_id.issuer, offering_id.namespace, offering_id.token), + ( + EVENT_ISSUER_TRANSFER_CANCELLED, + offering_id.issuer, + offering_id.namespace, + offering_id.token, + ), (current_issuer, proposed_new_issuer), ); @@ -3142,7 +3490,12 @@ impl RevoraRevenueShare { } /// Get the pending issuer transfer for an offering, if any. - pub fn get_pending_issuer_transfer(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Option
{ + pub fn get_pending_issuer_transfer( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option
{ let offering_id = OfferingId { issuer, namespace, token }; let pending_key = DataKey::PendingIssuerTransfer(offering_id); env.storage().persistent().get(&pending_key) @@ -3184,7 +3537,13 @@ impl RevoraRevenueShare { let offering = Self::get_offering(env.clone(), issuer.clone(), namespace, token.clone()) .expect("offering not found"); - if Self::is_blacklisted(env.clone(), issuer.clone(), offering.namespace.clone(), token.clone(), holder.clone()) { + if Self::is_blacklisted( + env.clone(), + issuer.clone(), + offering.namespace.clone(), + token.clone(), + holder.clone(), + ) { panic!("holder is blacklisted and cannot receive distribution"); } @@ -3192,7 +3551,14 @@ impl RevoraRevenueShare { let payout = 0i128; env.events().publish( (EVENT_DIST_CALC, issuer, offering.namespace, token), - (holder.clone(), total_revenue, total_supply, holder_balance, offering.revenue_share_bps, payout), + ( + holder.clone(), + total_revenue, + total_supply, + holder_balance, + offering.revenue_share_bps, + payout, + ), ); return payout; } @@ -3207,7 +3573,14 @@ impl RevoraRevenueShare { env.events().publish( (EVENT_DIST_CALC, issuer, offering.namespace, token), - (holder, total_revenue, total_supply, holder_balance, offering.revenue_share_bps, payout), + ( + holder, + total_revenue, + total_supply, + holder_balance, + offering.revenue_share_bps, + payout, + ), ); payout @@ -3223,8 +3596,8 @@ impl RevoraRevenueShare { token: Address, total_revenue: i128, ) -> i128 { - let offering = - Self::get_offering(env, issuer, namespace, token).expect("offering not found for token"); + let offering = Self::get_offering(env, issuer, namespace, token) + .expect("offering not found for token"); if total_revenue == 0 { return 0; @@ -3301,9 +3674,14 @@ impl RevoraRevenueShare { Self::require_not_paused(&env); // Verify offering exists and issuer is current - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); @@ -3322,18 +3700,21 @@ impl RevoraRevenueShare { // Emit appropriate event if is_update { - env.events() - .publish((EVENT_METADATA_UPDATED, issuer, namespace, token), metadata); + env.events().publish((EVENT_METADATA_UPDATED, issuer, namespace, token), metadata); } else { - env.events() - .publish((EVENT_METADATA_SET, issuer, namespace, token), metadata); + env.events().publish((EVENT_METADATA_SET, issuer, namespace, token), metadata); } Ok(()) } /// Retrieve metadata reference for an offering. - pub fn get_offering_metadata(env: Env, issuer: Address, namespace: Symbol, token: Address) -> Option { + pub fn get_offering_metadata( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> Option { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::OfferingMetadata(offering_id); env.storage().persistent().get(&key) @@ -3381,7 +3762,7 @@ impl RevoraRevenueShare { for ns_idx in 0..ns_count { let ns_key = DataKey::NamespaceItem(issuer.clone(), ns_idx); let namespace: Symbol = env.storage().persistent().get(&ns_key).unwrap(); - + let tenant_id = TenantId { issuer: issuer.clone(), namespace: namespace.clone() }; let count = Self::get_offering_count(env.clone(), issuer.clone(), namespace.clone()); total_offerings = total_offerings.saturating_add(count); @@ -3389,14 +3770,16 @@ impl RevoraRevenueShare { for i in 0..count { let item_key = DataKey::OfferItem(tenant_id.clone(), i); let offering: Offering = env.storage().persistent().get(&item_key).unwrap(); - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: offering.token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: offering.token.clone(), + }; // Sum audit summary (reported revenue) let summary_key = DataKey::AuditSummary(offering_id.clone()); - if let Some(summary) = env - .storage() - .persistent() - .get::(&summary_key) + if let Some(summary) = + env.storage().persistent().get::(&summary_key) { total_reported = total_reported.saturating_add(summary.total_revenue); total_reports = total_reports.saturating_add(summary.report_count); @@ -3421,11 +3804,7 @@ impl RevoraRevenueShare { /// Iterates the global issuer registry, capped at MAX_AGGREGATION_ISSUERS for gas safety. pub fn get_platform_aggregation(env: Env) -> AggregatedMetrics { let issuer_count_key = DataKey::IssuerCount; - let issuer_count: u32 = env - .storage() - .persistent() - .get(&issuer_count_key) - .unwrap_or(0); + let issuer_count: u32 = env.storage().persistent().get(&issuer_count_key).unwrap_or(0); let cap = core::cmp::min(issuer_count, Self::MAX_AGGREGATION_ISSUERS); @@ -3456,11 +3835,7 @@ impl RevoraRevenueShare { /// Return all registered issuer addresses (up to MAX_AGGREGATION_ISSUERS). pub fn get_all_issuers(env: Env) -> Vec
{ let issuer_count_key = DataKey::IssuerCount; - let issuer_count: u32 = env - .storage() - .persistent() - .get(&issuer_count_key) - .unwrap_or(0); + let issuer_count: u32 = env.storage().persistent().get(&issuer_count_key).unwrap_or(0); let cap = core::cmp::min(issuer_count, Self::MAX_AGGREGATION_ISSUERS); let mut issuers = Vec::new(&env); @@ -3474,7 +3849,12 @@ impl RevoraRevenueShare { } /// Return the total deposited revenue for a specific offering. - pub fn get_total_deposited_revenue(env: Env, issuer: Address, namespace: Symbol, token: Address) -> i128 { + pub fn get_total_deposited_revenue( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> i128 { let offering_id = OfferingId { issuer, namespace, token }; let key = DataKey::DepositedRevenue(offering_id); env.storage().persistent().get(&key).unwrap_or(0) @@ -3485,29 +3865,21 @@ impl RevoraRevenueShare { /// Set the platform fee in basis points. Admin-only. /// Maximum value is 5 000 bps (50 %). Pass 0 to disable. pub fn set_platform_fee(env: Env, fee_bps: u32) -> Result<(), RevoraError> { - let admin: Address = env - .storage() - .persistent() - .get(&DataKey::Admin) - .ok_or(RevoraError::LimitReached)?; + let admin: Address = + env.storage().persistent().get(&DataKey::Admin).ok_or(RevoraError::LimitReached)?; admin.require_auth(); if fee_bps > MAX_PLATFORM_FEE_BPS { return Err(RevoraError::LimitReached); } - env.storage() - .persistent() - .set(&DataKey::PlatformFeeBps, &fee_bps); + env.storage().persistent().set(&DataKey::PlatformFeeBps, &fee_bps); Ok(()) } /// Return the current platform fee in basis points (default 0). pub fn get_platform_fee(env: Env) -> u32 { - env.storage() - .persistent() - .get(&DataKey::PlatformFeeBps) - .unwrap_or(0) + env.storage().persistent().get(&DataKey::PlatformFeeBps).unwrap_or(0) } /// Calculate the platform fee for a given amount. @@ -3529,7 +3901,8 @@ impl RevoraRevenueShare { ) -> Result<(), RevoraError> { Self::require_not_frozen(&env)?; let current_issuer = - Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()).ok_or(RevoraError::OfferingNotFound)?; + Self::get_current_issuer(&env, issuer.clone(), namespace.clone(), token.clone()) + .ok_or(RevoraError::OfferingNotFound)?; if current_issuer != issuer { return Err(RevoraError::OfferingNotFound); } @@ -3537,7 +3910,11 @@ impl RevoraRevenueShare { if fee_bps > MAX_PLATFORM_FEE_BPS { return Err(RevoraError::LimitReached); } - let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + let offering_id = OfferingId { + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + }; let key = DataKey::OfferingFeeBps(offering_id, asset.clone()); env.storage().persistent().set(&key, &fee_bps); env.events().publish((EVENT_FEE_CONFIG, issuer, namespace, token), (asset, fee_bps, true)); @@ -3545,24 +3922,34 @@ impl RevoraRevenueShare { } /// Set platform-level per-asset fee in bps. Admin only. Overrides global platform fee for this asset. - pub fn set_platform_fee_per_asset(env: Env, admin: Address, asset: Address, fee_bps: u32) -> Result<(), RevoraError> { + pub fn set_platform_fee_per_asset( + env: Env, + admin: Address, + asset: Address, + fee_bps: u32, + ) -> Result<(), RevoraError> { admin.require_auth(); - let stored_admin: Address = env.storage().persistent().get(&DataKey::Admin).ok_or(RevoraError::LimitReached)?; + let stored_admin: Address = + env.storage().persistent().get(&DataKey::Admin).ok_or(RevoraError::LimitReached)?; if admin != stored_admin { return Err(RevoraError::NotAuthorized); } if fee_bps > MAX_PLATFORM_FEE_BPS { return Err(RevoraError::LimitReached); } - env.storage() - .persistent() - .set(&DataKey::PlatformFeePerAsset(asset.clone()), &fee_bps); + env.storage().persistent().set(&DataKey::PlatformFeePerAsset(asset.clone()), &fee_bps); env.events().publish((EVENT_FEE_CONFIG, admin, asset), (fee_bps, false)); Ok(()) } /// Effective fee bps for (offering, asset). Precedence: offering fee > platform per-asset > global platform fee. - pub fn get_effective_fee_bps(env: Env, issuer: Address, namespace: Symbol, token: Address, asset: Address) -> u32 { + pub fn get_effective_fee_bps( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + asset: Address, + ) -> u32 { let offering_id = OfferingId { issuer, namespace, token }; let offering_key = DataKey::OfferingFeeBps(offering_id, asset.clone()); if let Some(bps) = env.storage().persistent().get::(&offering_key) { @@ -3600,6 +3987,8 @@ pub mod vesting; #[cfg(test)] mod vesting_test; +#[cfg(test)] +mod chunking_tests; mod test; mod test_auth; mod test_cross_contract; diff --git a/src/test.rs b/src/test.rs index 36420a12..8ee43228 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,13 +1,14 @@ #![cfg(test)] +#![allow(unused_variables, dead_code, unused_imports)] +use crate::{ + ProposalAction, RevoraError, RevoraRevenueShare, RevoraRevenueShareClient, RoundingMode, +}; use soroban_sdk::{ symbol_short, testutils::{Address as _, Events as _, Ledger as _}, token, vec, Address, Env, IntoVal, String as SdkString, Symbol, Vec, }; -use crate::{ - ProposalAction, RevoraError, RevoraRevenueShare, RevoraRevenueShareClient, RoundingMode, -}; // ── helper ──────────────────────────────────────────────────── @@ -22,13 +23,10 @@ const FUZZ_ITERATIONS: usize = 128; const STORAGE_STRESS_OFFERING_COUNT: u32 = 100; fn next_u64(seed: &mut u64) -> u64 { - // Deterministic LCG for repeatable pseudo-random test values. *seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407); - *seed = seed - .wrapping_mul(6_364_136_223_846_793_005) - .wrapping_add(1_442_695_040_888_963_407); + *seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407); *seed } @@ -70,6 +68,7 @@ fn register_offering_emits_exact_event() { let issuer = Address::generate(&env); let token = Address::generate(&env); + let payout_asset = Address::generate(&env); let bps: u32 = 1_500; client.register_offering(&issuer, &symbol_short!("def"), &token, &bps, &token, &0); @@ -101,7 +100,15 @@ fn report_revenue_emits_exact_event() { let period_id: u64 = 42; client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &amount, &period_id, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &token, + &amount, + &period_id, + &false, + ); let empty_bl = Vec::
::new(&env); assert_eq!( @@ -120,12 +127,7 @@ fn report_revenue_emits_exact_event() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (amount, period_id, empty_bl.clone()).into_val(&env), ), @@ -136,12 +138,7 @@ fn report_revenue_emits_exact_event() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (amount, period_id).into_val(&env), ), @@ -166,7 +163,15 @@ fn combined_flow_preserves_event_order() { let period_id: u64 = 1; client.register_offering(&issuer, &symbol_short!("def"), &token, &bps, &token, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &amount, &period_id, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &token, + &amount, + &period_id, + &false, + ); let events = env.events().all(); assert_eq!(events.len(), 5); @@ -188,12 +193,7 @@ fn combined_flow_preserves_event_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (amount, period_id, empty_bl.clone()).into_val(&env), ), @@ -204,12 +204,7 @@ fn combined_flow_preserves_event_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (amount, period_id).into_val(&env), ), @@ -226,19 +221,35 @@ fn complex_mixed_flow_events_in_order() { let client = RevoraRevenueShareClient::new(&env, &contract_id); let issuer_a = Address::generate(&env); - let issuer = issuer_a.clone(); + let issuer = issuer_a.clone(); let issuer_b = Address::generate(&env); - let issuer = issuer_b.clone(); + let issuer = issuer_b.clone(); let token_x = Address::generate(&env); let token_y = Address::generate(&env); - - // Interleave: register A, register B, report A, report B client.register_offering(&issuer_a, &symbol_short!("def"), &token_x, &500, &token_x, &0); client.register_offering(&issuer_b, &symbol_short!("def"), &token_y, &750, &token_y, &0); - client.report_revenue(&issuer_a, &symbol_short!("def"), &token_x, &token_x, &100_000, &1, &false); - client.report_revenue(&issuer_b, &symbol_short!("def"), &token_y, &token_y, &200_000, &1, &false); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_x, &500, &token_x, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_y, &750, &token_y, &0); + client.report_revenue( + &issuer_a, + &symbol_short!("def"), + &token_x, + &token_x, + &100_000, + &1, + &false, + ); + client.report_revenue( + &issuer_b, + &symbol_short!("def"), + &token_y, + &token_y, + &200_000, + &1, + &false, + ); let events = env.events().all(); assert_eq!(events.len(), 10); @@ -265,12 +276,7 @@ fn complex_mixed_flow_events_in_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer_a.clone(), - token_x.clone(), - token_x.clone(), - ) + (symbol_short!("rev_inia"), issuer_a.clone(), token_x.clone(), token_x.clone(),) .into_val(&env), (100_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -281,12 +287,7 @@ fn complex_mixed_flow_events_in_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer_a.clone(), - token_x.clone(), - token_x.clone(), - ) + (symbol_short!("rev_repa"), issuer_a.clone(), token_x.clone(), token_x.clone(),) .into_val(&env), (100_000i128, 1u64).into_val(&env), ), @@ -297,12 +298,7 @@ fn complex_mixed_flow_events_in_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer_b.clone(), - token_y.clone(), - token_y.clone(), - ) + (symbol_short!("rev_inia"), issuer_b.clone(), token_y.clone(), token_y.clone(),) .into_val(&env), (200_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -313,12 +309,7 @@ fn complex_mixed_flow_events_in_order() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer_b.clone(), - token_y.clone(), - token_y.clone(), - ) + (symbol_short!("rev_repa"), issuer_b.clone(), token_y.clone(), token_y.clone(),) .into_val(&env), (200_000i128, 1u64).into_val(&env), ), @@ -407,12 +398,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (10_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -423,12 +409,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (10_000i128, 1u64).into_val(&env), ), @@ -439,12 +420,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (20_000i128, 2u64, empty_bl.clone()).into_val(&env), ), @@ -455,12 +431,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (20_000i128, 2u64).into_val(&env), ), @@ -471,12 +442,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (30_000i128, 3u64, empty_bl.clone()).into_val(&env), ), @@ -487,12 +453,7 @@ fn multiple_revenue_reports_same_offering() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (30_000i128, 3u64).into_val(&env), ), @@ -542,12 +503,7 @@ fn same_issuer_different_tokens() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token_x.clone(), - token_x.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token_x.clone(), token_x.clone()) .into_val(&env), (500_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -558,12 +514,7 @@ fn same_issuer_different_tokens() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token_x.clone(), - token_x.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token_x.clone(), token_x.clone()) .into_val(&env), (500_000i128, 1u64).into_val(&env), ), @@ -574,12 +525,7 @@ fn same_issuer_different_tokens() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token_y.clone(), - token_y.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token_y.clone(), token_y.clone()) .into_val(&env), (750_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -590,12 +536,7 @@ fn same_issuer_different_tokens() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token_y.clone(), - token_y.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token_y.clone(), token_y.clone()) .into_val(&env), (750_000i128, 1u64).into_val(&env), ), @@ -636,12 +577,7 @@ fn topic_symbols_are_distinct() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (1_000_000i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -652,12 +588,7 @@ fn topic_symbols_are_distinct() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (1_000_000i128, 1u64).into_val(&env), ), @@ -696,12 +627,7 @@ fn rev_rep_topics_include_token_address() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (999i128, 7u64, empty_bl.clone()).into_val(&env), ), @@ -712,12 +638,7 @@ fn rev_rep_topics_include_token_address() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (999i128, 7u64).into_val(&env), ), @@ -811,12 +732,7 @@ fn zero_amount_revenue_report() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (0i128, 1u64, empty_bl.clone()).into_val(&env), ), @@ -827,12 +743,7 @@ fn zero_amount_revenue_report() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (0i128, 1u64).into_val(&env), ), @@ -853,7 +764,15 @@ fn large_revenue_amount() { let large_amount: i128 = i128::MAX; client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &large_amount, &u64::MAX, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &token, + &large_amount, + &u64::MAX, + &false, + ); let empty_bl = Vec::
::new(&env); assert_eq!( @@ -872,12 +791,7 @@ fn large_revenue_amount() { ), ( contract_id.clone(), - ( - symbol_short!("rev_inia"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_inia"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (large_amount, u64::MAX, empty_bl.clone()).into_val(&env), ), @@ -888,12 +802,7 @@ fn large_revenue_amount() { ), ( contract_id.clone(), - ( - symbol_short!("rev_repa"), - issuer.clone(), - token.clone(), - token.clone() - ) + (symbol_short!("rev_repa"), issuer.clone(), token.clone(), token.clone()) .into_val(&env), (large_amount, u64::MAX).into_val(&env), ), @@ -915,7 +824,15 @@ fn negative_revenue_amount() { // Negative revenue is rejected by input validation (#35). let negative: i128 = -500_000; client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &token, &negative, &99, &false); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &token, + &negative, + &99, + &false, + ); assert!(r.is_err()); } @@ -943,26 +860,26 @@ fn it_emits_versioned_events() { // enable versioned events for this test env.as_contract(&contract_id, || { - env.storage() - .persistent() - .set(&crate::DataKey::EventVersioningEnabled, &true); + env.storage().persistent().set(&crate::DataKey::EventVersioningEnabled, &true); }); client.register_offering(&issuer, &symbol_short!("def"), &token, &bps, &payout, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout, &amount, &period_id, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout, + &amount, + &period_id, + &false, + ); let events = env.events().all(); let expected = ( contract_id.clone(), (symbol_short!("ofr_reg1"), issuer.clone()).into_val(&env), - ( - crate::EVENT_SCHEMA_VERSION, - token.clone(), - bps, - payout.clone(), - ) - .into_val(&env), + (crate::EVENT_SCHEMA_VERSION, token.clone(), bps, payout.clone()).into_val(&env), ); assert!(events.contains(&expected)); @@ -984,15 +901,21 @@ fn fuzz_period_and_amount_boundaries_do_not_panic() { let mut accepted = 0usize; for amount in BOUNDARY_AMOUNTS { for period in BOUNDARY_PERIODS { - let r = - client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &amount, &period, &false); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &amount, + &period, + &false, + ); if r.is_ok() { accepted += 1; } } } - let calls = (BOUNDARY_AMOUNTS.len() * BOUNDARY_PERIODS.len()) as u64; // Each report_revenue call emits 2 events: a specific event (rev_init/rev_ovrd/rev_rej) // plus the backward-compatible rev_rep event. @@ -1035,13 +958,20 @@ fn fuzz_period_and_amount_repeatable_sweep_do_not_panic() { amount = amount.saturating_neg().max(0); } - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &amount, &period, &false); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &amount, + &period, + &false, + ); if r.is_ok() { accepted += 1; } } - // Each report_revenue call emits 2 events (specific + backward-compatible rev_rep). assert_eq!(env.events().all().len(), (FUZZ_ITERATIONS * 2) as u32); @@ -1071,18 +1001,135 @@ fn register_n(env: &Env, client: &RevoraRevenueShareClient, issuer: &Address, n: for i in 0..n { let token = Address::generate(env); let payout_asset = Address::generate(env); - client.register_offering(issuer, &symbol_short!("def"), &token, &(100 + i), &payout_asset, &0); + client.register_offering( + issuer, + &symbol_short!("def"), + &token, + &(100 + i), + &payout_asset, + &0, + ); + } +} + +#[test] +fn get_revenue_range_chunk_matches_full_sum() { + let env = Env::default(); + env.mock_all_auths(); + + let client = make_client(&env); + + let issuer = Address::generate(&env); + let token = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1000u32, &token, &0i128); + + // Report revenue for periods 1..=10 + for p in 1u64..=10u64 { + client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &100i128, &p, &false); } + + // Full sum + let full = client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1u64, &10u64); + + // Sum in chunks of 3 + let mut cursor = 1u64; + let mut acc: i128 = 0; + loop { + let (partial, next) = client.get_revenue_range_chunk( + &issuer, + &symbol_short!("def"), + &token, + &cursor, + &10u64, + &3u32, + ); + acc += partial; + if let Some(n) = next { + cursor = n; + } else { + break; + } + } + + assert_eq!(full, acc); +} + +#[test] +fn pending_periods_page_and_claimable_chunk_consistent() { + let env = Env::default(); + env.mock_all_auths(); + + let client = make_client(&env); + + let issuer = Address::generate(&env); + let token = Address::generate(&env); + let holder = Address::generate(&env); + + client.register_offering(&issuer, &symbol_short!("def"), &token, &1000u32, &token, &0i128); + + // Deposit periods 1..=8 via deposit_revenue + for p in 1u64..=8u64 { + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &token, &1000i128, &p); + } + + // Set holder share + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &1000u32); + + // get_pending_periods full + let full = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder); + + // Page through with limit 3 + let mut cursor = 0u32; + let mut all = Vec::new(&env); + loop { + let (page, next) = client.get_pending_periods_page( + &issuer, + &symbol_short!("def"), + &token, + &holder, + &cursor, + &3u32, + ); + for i in 0..page.len() { + all.push_back(page.get(i).unwrap()); + } + if let Some(n) = next { + cursor = n; + } else { + break; + } + } + + // Compare lengths + assert_eq!(full.len(), all.len()); + + // Now check claimable chunk matches full + let full_claim = client.get_claimable(&issuer, &symbol_short!("def"), &token, &holder); + + // Sum claimable in chunks from index 0, count 2 + let mut idx = 0u32; + let mut acc: i128 = 0; + loop { + let (partial, next) = client.get_claimable_chunk( + &issuer, + &symbol_short!("def"), + &token, + &holder, + &idx, + &2u32, + ); + acc += partial; + if let Some(n) = next { + idx = n; + } else { + break; + } + } + assert_eq!(full_claim, acc); } /// Helper (#30): create env, client, and one registered offering. Returns (env, client, issuer, token, payout_asset). -fn setup_with_offering() -> ( - Env, - RevoraRevenueShareClient<'static>, - Address, - Address, - Address, -) { +fn setup_with_offering() -> (Env, RevoraRevenueShareClient<'static>, Address, Address, Address) { let (env, client, issuer) = setup(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1094,17 +1141,17 @@ fn setup_with_offering() -> ( fn setup_with_revenue_report( amount: i128, period_id: u64, -) -> ( - Env, - RevoraRevenueShareClient<'static>, - Address, - Address, - Address, - i128, - u64, -) { +) -> (Env, RevoraRevenueShareClient<'static>, Address, Address, Address, i128, u64) { let (env, client, issuer, token, payout_asset) = setup_with_offering(); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &amount, &period_id, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &amount, + &period_id, + &false, + ); (env, client, issuer, token, payout_asset, amount, period_id) } @@ -1151,12 +1198,14 @@ fn multi_page_cursor_progression() { assert_eq!(cursor1, Some(3)); // Second page: items 3..6 - let (page2, cursor2) = client.get_offerings_page(&issuer, &symbol_short!("def"), &cursor1.unwrap_or(0), &3); + let (page2, cursor2) = + client.get_offerings_page(&issuer, &symbol_short!("def"), &cursor1.unwrap_or(0), &3); assert_eq!(page2.len(), 3); assert_eq!(cursor2, Some(6)); // Third (final) page: items 6..7 - let (page3, cursor3) = client.get_offerings_page(&issuer, &symbol_short!("def"), &cursor2.unwrap_or(0), &3); + let (page3, cursor3) = + client.get_offerings_page(&issuer, &symbol_short!("def"), &cursor2.unwrap_or(0), &3); assert_eq!(page3.len(), 1); assert_eq!(cursor3, None); } @@ -1240,8 +1289,7 @@ fn offerings_preserve_correct_data() { fn separate_issuers_have_independent_pages() { let (env, client, issuer_a) = setup(); let issuer_b = Address::generate(&env); - let issuer = issuer_b.clone(); - + let issuer = issuer_b.clone(); register_n(&env, &client, &issuer_a, 3); register_n(&env, &client, &issuer_b, 5); @@ -1278,7 +1326,7 @@ fn add_marks_investor_as_blacklisted() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1297,7 +1345,7 @@ fn remove_unmarks_investor() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1316,7 +1364,7 @@ fn get_blacklist_returns_all_blocked_investors() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1343,6 +1391,7 @@ fn get_blacklist_empty_before_any_add() { let client = make_client(&env); let token = Address::generate(&env); + let issuer = Address::generate(&env); assert_eq!(client.get_blacklist(&issuer, &symbol_short!("def"), &token).len(), 0); } @@ -1354,7 +1403,7 @@ fn double_add_is_idempotent() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1374,7 +1423,7 @@ fn remove_nonexistent_is_idempotent() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1394,7 +1443,7 @@ fn blacklist_is_scoped_per_offering() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token_a = Address::generate(&env); let token_b = Address::generate(&env); @@ -1412,7 +1461,7 @@ fn removing_from_one_offering_does_not_affect_another() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token_a = Address::generate(&env); let token_b = Address::generate(&env); @@ -1434,7 +1483,7 @@ fn blacklist_add_emits_event() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1453,7 +1502,7 @@ fn blacklist_remove_emits_event() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1475,7 +1524,7 @@ fn blacklisted_investor_excluded_from_distribution_filter() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1486,7 +1535,10 @@ fn blacklisted_investor_excluded_from_distribution_filter() { client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &blocked); let investors = [allowed.clone(), blocked.clone()]; - let eligible = investors.iter().filter(|inv| !client.is_blacklisted(&issuer, &symbol_short!("def"), &token, &inv)).count(); + let eligible = investors + .iter() + .filter(|inv| !client.is_blacklisted(&issuer, &symbol_short!("def"), &token, inv)) + .count(); assert_eq!(eligible, 1); } @@ -1497,7 +1549,7 @@ fn blacklist_takes_precedence_over_whitelist() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -1519,12 +1571,13 @@ fn blacklist_add_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); let bad_actor = Address::generate(&env); - let issuer = bad_actor.clone(); + let issuer = bad_actor.clone(); let token = Address::generate(&env); let victim = Address::generate(&env); - client.blacklist_add(&bad_actor, &issuer, &symbol_short!("def"), &token, &victim); + let r = client.try_blacklist_add(&bad_actor, &issuer, &symbol_short!("def"), &token, &victim); + assert!(r.is_err()); } #[test] @@ -1533,12 +1586,14 @@ fn blacklist_remove_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); let bad_actor = Address::generate(&env); - let issuer = bad_actor.clone(); + let issuer = bad_actor.clone(); let token = Address::generate(&env); let investor = Address::generate(&env); - client.blacklist_remove(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + let r = + client.try_blacklist_remove(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + assert!(r.is_err()); } // ── whitelist CRUD ──────────────────────────────────────────── @@ -1547,14 +1602,13 @@ fn blacklist_remove_requires_auth() { fn whitelist_add_marks_investor_as_whitelisted() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); - assert!(!client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor)); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor)); @@ -1564,11 +1618,11 @@ fn whitelist_add_marks_investor_as_whitelisted() { fn whitelist_remove_unmarks_investor() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); @@ -1582,12 +1636,12 @@ fn get_whitelist_returns_all_approved_investors() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); - let inv_a = Address::generate(&env); - let inv_b = Address::generate(&env); - let inv_c = Address::generate(&env); + let token = Address::generate(&env); + let inv_a = Address::generate(&env); + let inv_b = Address::generate(&env); + let inv_c = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &inv_a); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &inv_b); @@ -1612,7 +1666,9 @@ fn get_whitelist_empty_before_any_add() { for period_id in 1..=100_u64 { client.report_revenue( - &issuer, &symbol_short!("def"), &token, + &issuer, + &symbol_short!("def"), + &token, &payout_asset, &(period_id as i128 * 10_000), &period_id, @@ -1629,11 +1685,11 @@ fn get_whitelist_empty_before_any_add() { fn whitelist_double_add_is_idempotent() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); @@ -1646,11 +1702,11 @@ fn whitelist_double_add_is_idempotent() { fn whitelist_remove_nonexistent_is_idempotent() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); // must not panic @@ -1663,17 +1719,17 @@ fn whitelist_remove_nonexistent_is_idempotent() { fn whitelist_is_scoped_per_offering() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token_a, &investor); - assert!( client.is_whitelisted(&issuer, &symbol_short!("def"), &token_a, &investor)); + assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token_a, &investor)); assert!(!client.is_whitelisted(&issuer, &symbol_short!("def"), &token_b, &investor)); } @@ -1681,12 +1737,12 @@ fn whitelist_is_scoped_per_offering() { fn whitelist_removing_from_one_offering_does_not_affect_another() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token_a, &investor); @@ -1694,7 +1750,7 @@ fn whitelist_removing_from_one_offering_does_not_affect_another() { client.whitelist_remove(&admin, &issuer, &symbol_short!("def"), &token_a, &investor); assert!(!client.is_whitelisted(&issuer, &symbol_short!("def"), &token_a, &investor)); - assert!( client.is_whitelisted(&issuer, &symbol_short!("def"), &token_b, &investor)); + assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token_b, &investor)); } // ── whitelist event emission ────────────────────────────────── @@ -1703,11 +1759,11 @@ fn whitelist_removing_from_one_offering_does_not_affect_another() { fn whitelist_add_emits_event() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); let before = env.events().all().len(); @@ -1719,11 +1775,11 @@ fn whitelist_add_emits_event() { fn whitelist_remove_emits_event() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); @@ -1738,25 +1794,25 @@ fn whitelist_remove_emits_event() { fn whitelist_enabled_only_includes_whitelisted_investors() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let whitelisted = Address::generate(&env); - let not_listed = Address::generate(&env); + let not_listed = Address::generate(&env); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &whitelisted); let investors = [whitelisted.clone(), not_listed.clone()]; let whitelist_enabled = client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token); - + let eligible = investors .iter() .filter(|inv| { - let blacklisted = client.is_blacklisted(&issuer, &symbol_short!("def"), &token, &inv); - let whitelisted = client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &inv); - + let blacklisted = client.is_blacklisted(&issuer, &symbol_short!("def"), &token, inv); + let whitelisted = client.is_whitelisted(&issuer, &symbol_short!("def"), &token, inv); + if blacklisted { return false; } @@ -1774,23 +1830,24 @@ fn whitelist_enabled_only_includes_whitelisted_investors() { fn whitelist_disabled_includes_all_non_blacklisted() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); - let token = Address::generate(&env); - let inv_a = Address::generate(&env); - let inv_b = Address::generate(&env); + let client = make_client(&env); + let token = Address::generate(&env); + let inv_a = Address::generate(&env); + let inv_b = Address::generate(&env); + let issuer = Address::generate(&env); // No whitelist entries - whitelist disabled assert!(!client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token)); let investors = [inv_a.clone(), inv_b.clone()]; let whitelist_enabled = client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token); - + let eligible = investors .iter() .filter(|inv| { - let blacklisted = client.is_blacklisted(&issuer, &symbol_short!("def"), &token, &inv); - let whitelisted = client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &inv); - + let blacklisted = client.is_blacklisted(&issuer, &symbol_short!("def"), &token, inv); + let whitelisted = client.is_whitelisted(&issuer, &symbol_short!("def"), &token, inv); + if blacklisted { return false; } @@ -1808,11 +1865,11 @@ fn whitelist_disabled_includes_all_non_blacklisted() { fn blacklist_overrides_whitelist() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); // Add to both whitelist and blacklist @@ -1824,7 +1881,7 @@ fn blacklist_overrides_whitelist() { let is_eligible = { let blacklisted = client.is_blacklisted(&issuer, &symbol_short!("def"), &token, &investor); let whitelisted = client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor); - + if blacklisted { false } else if whitelist_enabled { @@ -1843,28 +1900,31 @@ fn blacklist_overrides_whitelist() { #[should_panic] fn whitelist_add_requires_auth() { let env = Env::default(); // no mock_all_auths - let client = make_client(&env); + let client = make_client(&env); let bad_actor = Address::generate(&env); - let issuer = bad_actor.clone(); + let issuer = bad_actor.clone(); - let token = Address::generate(&env); - let investor = Address::generate(&env); + let token = Address::generate(&env); + let investor = Address::generate(&env); - client.whitelist_add(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + let r = client.try_whitelist_add(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + assert!(r.is_err()); } #[test] #[should_panic] fn whitelist_remove_requires_auth() { let env = Env::default(); // no mock_all_auths - let client = make_client(&env); + let client = make_client(&env); let bad_actor = Address::generate(&env); - let issuer = bad_actor.clone(); + let issuer = bad_actor.clone(); - let token = Address::generate(&env); - let investor = Address::generate(&env); + let token = Address::generate(&env); + let investor = Address::generate(&env); - client.whitelist_remove(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + let r = + client.try_whitelist_remove(&bad_actor, &issuer, &symbol_short!("def"), &token, &investor); + assert!(r.is_err()); } // ── large whitelist handling ────────────────────────────────── @@ -1875,15 +1935,15 @@ fn large_whitelist_operations() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); // Add 50 investors to whitelist let mut investors = soroban_sdk::Vec::new(&env); for _ in 0..50 { let inv = Address::generate(&env); - let issuer = inv.clone(); + let issuer = inv.clone(); client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &inv); investors.push_back(inv); } @@ -1893,7 +1953,12 @@ fn large_whitelist_operations() { // Verify all are whitelisted for i in 0..investors.len() { - assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investors.get(i).unwrap())); + assert!(client.is_whitelisted( + &issuer, + &symbol_short!("def"), + &token, + &investors.get(i).unwrap() + )); } } @@ -1903,20 +1968,20 @@ fn large_whitelist_operations() { fn repeated_whitelist_operations_on_same_address() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); // Add, remove, add again client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor)); - + client.whitelist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(!client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor)); - + client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(client.is_whitelisted(&issuer, &symbol_short!("def"), &token, &investor)); } @@ -1927,18 +1992,18 @@ fn repeated_whitelist_operations_on_same_address() { fn whitelist_enabled_when_non_empty() { let env = Env::default(); env.mock_all_auths(); - let client = make_client(&env); + let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); - let token = Address::generate(&env); + let token = Address::generate(&env); let investor = Address::generate(&env); assert!(!client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token)); - + client.whitelist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token)); - + client.whitelist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); assert!(!client.is_whitelist_enabled(&issuer, &symbol_short!("def"), &token)); } @@ -1954,16 +2019,19 @@ fn register_offering_rejects_bps_over_10000() { let token = Address::generate(&env); let payout_asset = Address::generate(&env); - let result = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &10_001, &payout_asset, &0); + let result = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token, + &10_001, + &payout_asset, + &0, + ); assert!( result.is_err(), "contract must return Err(RevoraError::InvalidRevenueShareBps) for bps > 10000" ); - assert_eq!( - RevoraError::InvalidRevenueShareBps as u32, - 1, - "error code for integrators" - ); + assert_eq!(RevoraError::InvalidRevenueShareBps as u32, 1, "error code for integrators"); } #[test] @@ -1975,7 +2043,14 @@ fn register_offering_accepts_bps_exactly_10000() { let token = Address::generate(&env); let payout_asset = Address::generate(&env); - let result = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &10_000, &payout_asset, &0); + let result = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token, + &10_000, + &payout_asset, + &0, + ); assert!(result.is_ok()); } @@ -1999,8 +2074,12 @@ fn storage_stress_many_offerings_no_panic() { register_n(&env, &client, &issuer, STORAGE_STRESS_OFFERING_COUNT); let count = client.get_offering_count(&issuer, &symbol_short!("def")); assert_eq!(count, STORAGE_STRESS_OFFERING_COUNT); - let (page, cursor) = - client.get_offerings_page(&issuer, &symbol_short!("def"), &(STORAGE_STRESS_OFFERING_COUNT - 5), &10); + let (page, cursor) = client.get_offerings_page( + &issuer, + &symbol_short!("def"), + &(STORAGE_STRESS_OFFERING_COUNT - 5), + &10, + ); assert_eq!(page.len(), 5); assert_eq!(cursor, None); } @@ -2015,16 +2094,16 @@ fn multiple_reports_same_period_accumulate() { client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &3_000, &7, &false); client.report_revenue(&issuer, &symbol_short!("def"), &token, &token, &2_000, &7, &true); // Use true for override to test accumulation if intended, but wait... - // Actually, report_revenue in lib.rs now OVERWRITES if override_existing is true. - // beda819 wanted accumulation. - // If I want accumulation, I should change lib.rs to accumulate even on override? - // Let's re-read lib.rs implementation I just made. - /* - if override_existing { - cumulative_revenue = cumulative_revenue.checked_sub(existing_amount)...checked_add(amount)... - reports.set(period_id, (amount, current_timestamp)); - } - */ + // Actually, report_revenue in lib.rs now OVERWRITES if override_existing is true. + // beda819 wanted accumulation. + // If I want accumulation, I should change lib.rs to accumulate even on override? + // Let's re-read lib.rs implementation I just made. + /* + if override_existing { + cumulative_revenue = cumulative_revenue.checked_sub(existing_amount)...checked_add(amount)... + reports.set(period_id, (amount, current_timestamp)); + } + */ // That overwrites. // If I want to support beda819's "accumulation", I should perhaps NOT use override_existing for accumulation. // But the tests in beda819 were: @@ -2049,7 +2128,9 @@ fn multiple_reports_same_period_accumulate() { for period_id in 1..=100_u64 { client.report_revenue( - &issuer, &symbol_short!("def"), &token, + &issuer, + &symbol_short!("def"), + &token, &payout_asset, &(period_id as i128 * 10_000), &period_id, @@ -2080,6 +2161,7 @@ fn empty_period_returns_zero() { let client = make_client(&env); let token = Address::generate(&env); + let issuer = Address::generate(&env); assert_eq!(client.get_revenue_by_period(&issuer, &symbol_short!("def"), &token, &99), 0); } @@ -2118,15 +2200,29 @@ fn gas_characterization_report_revenue_with_large_blacklist() { client.register_offering(&issuer, &symbol_short!("def"), &token, &500, &payout_asset, &0); for _ in 0..30 { - client.blacklist_add(&Address::generate(&env), &issuer, &symbol_short!("def"), &token, &Address::generate(&env)); + client.blacklist_add( + &Address::generate(&env), + &issuer, + &symbol_short!("def"), + &token, + &Address::generate(&env), + ); } let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); env.mock_all_auths(); client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &Address::generate(&env)); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000_000, &1, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000_000, + &1, + &false, + ); assert!(!env.events().all().is_empty()); } @@ -2169,7 +2265,15 @@ fn concentration_limit_not_set_allows_report_revenue() { let token = Address::generate(&env); let payout_asset = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); } #[test] @@ -2180,7 +2284,8 @@ fn set_concentration_limit_requires_offering_to_exist() { let issuer = Address::generate(&env); let token = Address::generate(&env); // No offering registered - let r = client.try_set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &false); + let r = + client.try_set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &false); assert!(r.is_err()); } @@ -2195,8 +2300,9 @@ fn set_concentration_limit_stores_config() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &false); let config = client.get_concentration_limit(&issuer, &symbol_short!("def"), &token); - assert_eq!(config.unwrap().max_bps, 5000); - assert!(!config.unwrap().enforce); + let cfg = config.unwrap(); + assert_eq!(cfg.max_bps, 5000); + assert!(!cfg.enforce); } #[test] @@ -2212,7 +2318,10 @@ fn report_concentration_emits_warning_when_over_limit() { let before = env.events().all().len(); client.report_concentration(&issuer, &symbol_short!("def"), &token, &6000); assert!(env.events().all().len() > before); - assert_eq!(client.get_current_concentration(&issuer, &symbol_short!("def"), &token), Some(6000)); + assert_eq!( + client.get_current_concentration(&issuer, &symbol_short!("def"), &token), + Some(6000) + ); } #[test] @@ -2226,7 +2335,10 @@ fn report_concentration_no_warning_when_below_limit() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &false); client.report_concentration(&issuer, &symbol_short!("def"), &token, &4000); - assert_eq!(client.get_current_concentration(&issuer, &symbol_short!("def"), &token), Some(4000)); + assert_eq!( + client.get_current_concentration(&issuer, &symbol_short!("def"), &token), + Some(4000) + ); } #[test] @@ -2240,7 +2352,15 @@ fn concentration_enforce_blocks_report_revenue_when_over_limit() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &true); client.report_concentration(&issuer, &symbol_short!("def"), &token, &6000); - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); assert!( r.is_err(), "report_revenue must fail when concentration exceeds limit with enforce=true" @@ -2258,9 +2378,25 @@ fn concentration_enforce_allows_report_revenue_when_at_or_below_limit() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &true); client.report_concentration(&issuer, &symbol_short!("def"), &token, &5000); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); client.report_concentration(&issuer, &symbol_short!("def"), &token, &4999); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &2, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &2, + &false, + ); } #[test] @@ -2275,12 +2411,21 @@ fn concentration_near_threshold_boundary() { client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &true); client.report_concentration(&issuer, &symbol_short!("def"), &token, &5001); - assert!(client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &token, &1_000, &1, &false).is_err()); - assert!(client - .try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false) + .try_report_revenue(&issuer, &symbol_short!("def"), &token, &token, &1_000, &1, &false) .is_err()); + assert!(client + .try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false + ) + .is_err()); } // --------------------------------------------------------------------------- @@ -2313,8 +2458,9 @@ fn audit_summary_aggregates_revenue_and_count() { client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &200, &2, &false); client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &300, &3, &false); let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert_eq!(summary.unwrap().total_revenue, 600); - assert_eq!(summary.unwrap().report_count, 3); + let s = summary.unwrap(); + assert_eq!(s.total_revenue, 600); + assert_eq!(s.report_count, 3); } #[test] @@ -2329,14 +2475,32 @@ fn audit_summary_per_offering_isolation() { let payout_asset_b = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token_a, &1_000, &payout_asset_a, &0); client.register_offering(&issuer, &symbol_short!("def"), &token_b, &1_000, &payout_asset_b, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token_a, &payout_asset_a, &1000, &1, &false); - client.report_revenue(&issuer, &symbol_short!("def"), &token_b, &payout_asset_b, &2000, &1, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token_a, + &payout_asset_a, + &1000, + &1, + &false, + ); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token_b, + &payout_asset_b, + &2000, + &1, + &false, + ); let sum_a = client.get_audit_summary(&issuer, &symbol_short!("def"), &token_a); let sum_b = client.get_audit_summary(&issuer, &symbol_short!("def"), &token_b); - assert_eq!(sum_a.unwrap().total_revenue, 1000); - assert_eq!(sum_a.unwrap().report_count, 1); - assert_eq!(sum_b.unwrap().total_revenue, 2000); - assert_eq!(sum_b.unwrap().report_count, 1); + let a = sum_a.unwrap(); + let b = sum_b.unwrap(); + assert_eq!(a.total_revenue, 1000); + assert_eq!(a.report_count, 1); + assert_eq!(b.total_revenue, 2000); + assert_eq!(b.report_count, 1); } // --------------------------------------------------------------------------- @@ -2390,7 +2554,10 @@ fn set_and_get_rounding_mode() { let token = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token, &0); - assert_eq!(client.get_rounding_mode(&issuer, &symbol_short!("def"), &token), RoundingMode::Truncation); + assert_eq!( + client.get_rounding_mode(&issuer, &symbol_short!("def"), &token), + RoundingMode::Truncation + ); let payout_asset = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); @@ -2400,7 +2567,10 @@ fn set_and_get_rounding_mode() { ); client.set_rounding_mode(&issuer, &symbol_short!("def"), &token, &RoundingMode::RoundHalfUp); - assert_eq!(client.get_rounding_mode(&issuer, &symbol_short!("def"), &token), RoundingMode::RoundHalfUp); + assert_eq!( + client.get_rounding_mode(&issuer, &symbol_short!("def"), &token), + RoundingMode::RoundHalfUp + ); } #[test] @@ -2410,7 +2580,12 @@ fn set_rounding_mode_requires_offering() { let client = make_client(&env); let issuer = Address::generate(&env); let token = Address::generate(&env); - let r = client.try_set_rounding_mode(&issuer, &symbol_short!("def"), &token, &RoundingMode::RoundHalfUp); + let r = client.try_set_rounding_mode( + &issuer, + &symbol_short!("def"), + &token, + &RoundingMode::RoundHalfUp, + ); assert!(r.is_err()); } @@ -2510,7 +2685,14 @@ fn deposit_revenue_fails_for_nonexistent_offering() { let (env, client, issuer, _token, payment_token, _contract_id) = claim_setup(); let unknown_token = Address::generate(&env); - let result = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &unknown_token, &payment_token, &100_000, &1); + let result = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &unknown_token, + &payment_token, + &100_000, + &1, + ); assert!(result.is_err()); } @@ -2519,7 +2701,14 @@ fn deposit_revenue_fails_for_duplicate_period() { let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); - let result = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); + let result = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &1, + ); assert!(result.is_err()); } @@ -2532,7 +2721,8 @@ fn deposit_revenue_fails_for_payment_token_mismatch() { // Try to deposit with a different payment token let (other_pt, other_admin) = create_payment_token(&env); mint_tokens(&env, &other_pt, &other_admin, &issuer, &1_000_000); - let result = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &other_pt, &100_000, &2); + let result = + client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &other_pt, &100_000, &2); assert!(result.is_err()); } @@ -2547,7 +2737,15 @@ fn report_revenue_rejects_mismatched_payout_asset() { let wrong_asset = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &wrong_asset, &1_000, &1, &false); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &wrong_asset, + &1_000, + &1, + &false, + ); assert!(r.is_err()); } @@ -2562,17 +2760,25 @@ fn deposit_revenue_rejects_mismatched_payout_asset_on_first_deposit() { let (configured_asset, configured_admin) = create_payment_token(&env); let (wrong_asset, wrong_admin) = create_payment_token(&env); - client.register_offering(&issuer, &symbol_short!("def"), &offering_token, &5_000, &configured_asset, &0); - mint_tokens(&env, &wrong_asset, &wrong_admin, &issuer, &1_000_000); - mint_tokens( - &env, - &configured_asset, - &configured_admin, + client.register_offering( &issuer, - &1_000_000, + &symbol_short!("def"), + &offering_token, + &5_000, + &configured_asset, + &0, ); + mint_tokens(&env, &wrong_asset, &wrong_admin, &issuer, &1_000_000); + mint_tokens(&env, &configured_asset, &configured_admin, &issuer, &1_000_000); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &offering_token, &wrong_asset, &100_000, &1); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &offering_token, + &wrong_asset, + &100_000, + &1, + ); assert!(r.is_err()); } @@ -2617,7 +2823,15 @@ fn deposit_revenue_requires_auth() { let issuer = Address::generate(&env); let tok = Address::generate(&env); // No mock_all_auths — should panic on require_auth - client.deposit_revenue(&issuer, &symbol_short!("def"), &tok, &Address::generate(&env), &100, &1); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &tok, + &Address::generate(&env), + &100, + &1, + ); + assert!(r.is_err()); } // ── set_holder_share tests ──────────────────────────────────── @@ -2647,7 +2861,13 @@ fn set_holder_share_fails_for_nonexistent_offering() { let unknown_token = Address::generate(&env); let holder = Address::generate(&env); - let result = client.try_set_holder_share(&issuer, &symbol_short!("def"), &unknown_token, &holder, &2_500); + let result = client.try_set_holder_share( + &issuer, + &symbol_short!("def"), + &unknown_token, + &holder, + &2_500, + ); assert!(result.is_err()); } @@ -2656,7 +2876,8 @@ fn set_holder_share_fails_for_bps_over_10000() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let holder = Address::generate(&env); - let result = client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &10_001); + let result = + client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &10_001); assert!(result.is_err()); } @@ -2665,7 +2886,8 @@ fn set_holder_share_accepts_bps_exactly_10000() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let holder = Address::generate(&env); - let result = client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &10_000); + let result = + client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &10_000); assert!(result.is_ok()); assert_eq!(client.get_holder_share(&issuer, &symbol_short!("def"), &token, &holder), 10_000); } @@ -2684,7 +2906,7 @@ fn set_holder_share_emits_event() { fn get_holder_share_returns_zero_for_unknown() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let unknown = Address::generate(&env); - assert_eq!(client.get_holder_share(&token, &unknown), 0); + assert_eq!(client.get_holder_share(&issuer, &symbol_short!("def"), &token, &unknown), 0); } // ── claim tests (core multi-period aggregation) ─────────────── @@ -2698,7 +2920,7 @@ fn claim_single_period() { client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); let payout = client.claim(&holder, &issuer, &symbol_short!("def"), &token, &0); - assert_eq!(payout_a, 50_000); // 50% of 100_000 + assert_eq!(payout, 50_000); // 50% of 100_000 assert_eq!(balance(&env, &payment_token, &holder), 50_000); } @@ -2730,7 +2952,7 @@ fn claim_max_periods_zero_claims_all() { } let payout = client.claim(&holder, &issuer, &symbol_short!("def"), &token, &0); - assert_eq!(payout_a, 50_000); // 100% of 5 * 10k + assert_eq!(payout, 50_000); // 100% of 5 * 10k } #[test] @@ -2932,7 +3154,14 @@ fn claim_requires_auth() { let client = RevoraRevenueShareClient::new(&env, &cid); let holder = Address::generate(&env); // No mock_all_auths — should panic on require_auth - client.claim(&holder, &Address::generate(&env), &0); + let r = client.try_claim( + &holder, + &Address::generate(&env), + &symbol_short!("def"), + &Address::generate(&env), + &0, + ); + assert!(r.is_err()); } // ── view function tests ─────────────────────────────────────── @@ -2991,7 +3220,7 @@ fn get_pending_periods_empty_for_new_holder() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let unknown = Address::generate(&env); - let pending = client.get_pending_periods(&token, &unknown); + let pending = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &unknown); assert_eq!(pending.len(), 0); } @@ -3030,7 +3259,7 @@ fn get_claimable_returns_zero_for_unknown_holder() { client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); let unknown = Address::generate(&env); - assert_eq!(client.get_claimable(&token, &unknown), 0); + assert_eq!(client.get_claimable(&issuer, &symbol_short!("def"), &token, &unknown), 0); } #[test] @@ -3122,7 +3351,7 @@ fn claim_many_periods_stress() { // Claim all 50 in one transaction let payout = client.claim(&holder, &issuer, &symbol_short!("def"), &token, &0); - assert_eq!(payout_a, 50_000); // 10% of 50 * 10k + assert_eq!(payout, 50_000); // 10% of 50 * 10k let pending = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder); assert_eq!(pending.len(), 0); @@ -3165,7 +3394,14 @@ fn get_claimable_stress_many_periods() { let period_count = 40_u64; let amount_per_period: i128 = 10_000; for i in 1..=period_count { - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &amount_per_period, &i); + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &amount_per_period, + &i, + ); } let claimable = client.get_claimable(&issuer, &symbol_short!("def"), &token, &holder); @@ -3249,8 +3485,14 @@ fn offering_isolation_claims_independent() { assert_eq!(payout_b, 50_000); // 100% of 50k // Verify token A claim doesn't affect token B pending - assert_eq!(client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder).len(), 0); - assert_eq!(client.get_pending_periods(&issuer, &symbol_short!("def"), &token_b, &holder).len(), 0); + assert_eq!( + client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder).len(), + 0 + ); + assert_eq!( + client.get_pending_periods(&issuer, &symbol_short!("def"), &token_b, &holder).len(), + 0 + ); } // =========================================================================== @@ -3261,9 +3503,9 @@ fn offering_isolation_claims_independent() { fn set_claim_delay_stores_and_returns_delay() { let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - assert_eq!(client.get_claim_delay(&token), 0); + assert_eq!(client.get_claim_delay(&issuer, &symbol_short!("def"), &token), 0); client.set_claim_delay(&issuer, &symbol_short!("def"), &token, &3600); - assert_eq!(client.get_claim_delay(&token), 3600); + assert_eq!(client.get_claim_delay(&issuer, &symbol_short!("def"), &token), 3600); } #[test] @@ -3363,7 +3605,8 @@ fn simulate_distribution_returns_correct_payouts() { shares.push_back((holder_a.clone(), 3_000u32)); shares.push_back((holder_b.clone(), 2_000u32)); - let result = client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100_000, &shares); + let result = + client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100_000, &shares); assert_eq!(result.total_distributed, 50_000); // 30% + 20% of 100k assert_eq!(result.payouts.len(), 2); assert_eq!(result.payouts.get(0).unwrap(), (holder_a, 30_000)); @@ -3375,7 +3618,8 @@ fn simulate_distribution_zero_holders() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let shares = Vec::new(&env); - let result = client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100_000, &shares); + let result = + client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100_000, &shares); assert_eq!(result.total_distributed, 0); assert_eq!(result.payouts.len(), 0); } @@ -3399,9 +3643,9 @@ fn simulate_distribution_read_only_no_state_change() { let mut shares = Vec::new(&env); shares.push_back((holder.clone(), 10_000u32)); - let _ = client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &1_000_000, &shares); + client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &1_000_000, &shares); let count_before = client.get_period_count(&issuer, &symbol_short!("def"), &token); - let _ = client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &999_999, &shares); + client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &999_999, &shares); assert_eq!(client.get_period_count(&issuer, &symbol_short!("def"), &token), count_before); } @@ -3413,7 +3657,8 @@ fn simulate_distribution_uses_rounding_mode() { let mut shares = Vec::new(&env); shares.push_back((holder.clone(), 3_333u32)); - let result = client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100, &shares); + let result = + client.simulate_distribution(&issuer, &symbol_short!("def"), &token, &100, &shares); assert_eq!(result.total_distributed, 33); assert_eq!(result.payouts.get(0).unwrap().1, 33); } @@ -3426,8 +3671,7 @@ fn simulate_distribution_uses_rounding_mode() { fn set_admin_once_succeeds() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); assert_eq!(client.get_admin(), Some(admin)); @@ -3437,8 +3681,7 @@ fn set_admin_once_succeeds() { fn set_admin_twice_fails() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); let other = Address::generate(&env); @@ -3450,8 +3693,7 @@ fn set_admin_twice_fails() { fn freeze_sets_flag_and_emits_event() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); assert!(!client.is_frozen()); @@ -3465,14 +3707,21 @@ fn freeze_sets_flag_and_emits_event() { fn frozen_blocks_register_offering() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let new_token = Address::generate(&env); let payout_asset = Address::generate(&env); client.set_admin(&admin); client.freeze(); - let r = client.try_register_offering(&issuer, &symbol_short!("def"), &new_token, &1_000, &payout_asset, &0); + let r = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &new_token, + &1_000, + &payout_asset, + &0, + ); assert!(r.is_err()); } @@ -3480,12 +3729,18 @@ fn frozen_blocks_register_offering() { fn frozen_blocks_deposit_revenue() { let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); client.freeze(); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &99); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &99, + ); assert!(r.is_err()); } @@ -3493,7 +3748,7 @@ fn frozen_blocks_deposit_revenue() { fn frozen_blocks_set_holder_share() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let holder = Address::generate(&env); @@ -3508,8 +3763,7 @@ fn frozen_allows_claim() { let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); let holder = Address::generate(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &10_000); client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); @@ -3525,8 +3779,7 @@ fn frozen_allows_claim() { fn freeze_succeeds_when_called_by_admin() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); env.mock_all_auths(); @@ -3535,8 +3788,6 @@ fn freeze_succeeds_when_called_by_admin() { assert!(client.is_frozen()); } - - // =========================================================================== // Snapshot-based distribution (#Snapshot) // =========================================================================== @@ -3563,6 +3814,7 @@ fn deposit_revenue_with_snapshot_succeeds_when_enabled() { let r = client.try_deposit_revenue_with_snapshot( &issuer, + &symbol_short!("def"), &token, &payment_token, &amount, @@ -3570,8 +3822,7 @@ fn deposit_revenue_with_snapshot_succeeds_when_enabled() { &snapshot_ref, ); assert!(r.is_ok()); - - assert_eq!(client.get_last_snapshot_ref(&issuer, &token), snapshot_ref); + assert_eq!(client.get_last_snapshot_ref(&issuer, &symbol_short!("def"), &token), snapshot_ref); assert_eq!(client.get_period_count(&issuer, &symbol_short!("def"), &token), 1); } @@ -3582,6 +3833,7 @@ fn deposit_revenue_with_snapshot_fails_when_disabled() { // Disabled by default let result = client.try_deposit_revenue_with_snapshot( &issuer, + &symbol_short!("def"), &token, &payment_token, &100_000, @@ -3591,8 +3843,6 @@ fn deposit_revenue_with_snapshot_fails_when_disabled() { // Should fail with SnapshotNotEnabled (12) assert!(result.is_err()); - let err = result.err(); - assert!(matches!(err, Ok(RevoraError::SnapshotNotEnabled))); } #[test] @@ -3602,11 +3852,20 @@ fn deposit_with_snapshot_enforces_monotonicity() { client.set_snapshot_config(&issuer, &symbol_short!("def"), &token, &true); // First deposit at ref 100 - client.deposit_revenue_with_snapshot(&issuer, &token, &payment_token, &10_000, &1, &100); + client.deposit_revenue_with_snapshot( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &10_000, + &1, + &100, + ); // Second deposit at ref 100 should fail (duplicate) let r2 = client.try_deposit_revenue_with_snapshot( &issuer, + &symbol_short!("def"), &token, &payment_token, &10_000, @@ -3615,18 +3874,26 @@ fn deposit_with_snapshot_enforces_monotonicity() { ); assert!(r2.is_err()); let err2 = r2.err(); - assert!(matches!(err2, Ok(RevoraError::OutdatedSnapshot))); + assert!(matches!(err2, Some(Ok(RevoraError::OutdatedSnapshot)))); // Third deposit at ref 99 should fail (outdated) - let r3 = - client.try_deposit_revenue_with_snapshot(&issuer, &token, &payment_token, &10_000, &3, &99); + let r3 = client.try_deposit_revenue_with_snapshot( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &10_000, + &3, + &99, + ); assert!(r3.is_err()); let err3 = r3.err(); - assert!(matches!(err3, Ok(RevoraError::OutdatedSnapshot))); + assert!(matches!(err3, Some(Ok(RevoraError::OutdatedSnapshot)))); // Fourth deposit at ref 101 should succeed let r4 = client.try_deposit_revenue_with_snapshot( &issuer, + &symbol_short!("def"), &token, &payment_token, &10_000, @@ -3634,7 +3901,7 @@ fn deposit_with_snapshot_enforces_monotonicity() { &101, ); assert!(r4.is_ok()); - assert_eq!(client.get_last_snapshot_ref(&issuer, &token), 101); + assert_eq!(client.get_last_snapshot_ref(&issuer, &symbol_short!("def"), &token), 101); } #[test] @@ -3644,7 +3911,15 @@ fn deposit_with_snapshot_emits_specialized_event() { client.set_snapshot_config(&issuer, &symbol_short!("def"), &token, &true); let before = env.events().all().len(); - client.deposit_revenue_with_snapshot(&issuer, &token, &payment_token, &10_000, &1, &1000); + client.deposit_revenue_with_snapshot( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &10_000, + &1, + &1000, + ); let all_events = env.events().all(); assert!(all_events.len() > before); @@ -3657,7 +3932,7 @@ fn set_snapshot_config_requires_offering() { let (env, client, issuer, _token, _payment_token, _contract_id) = claim_setup(); let unknown_token = Address::generate(&env); - let r = client.try_set_snapshot_config(&issuer, &unknown_token, &true); + let r = client.try_set_snapshot_config(&issuer, &symbol_short!("def"), &unknown_token, &true); assert!(r.is_err()); } @@ -3670,7 +3945,7 @@ fn set_snapshot_config_requires_auth() { let token = Address::generate(&env); // No mock_all_auths - let result = client.try_set_snapshot_config(&issuer, &token, &true); + let result = client.try_set_snapshot_config(&issuer, &symbol_short!("def"), &token, &true); assert!(result.is_err()); } @@ -3691,8 +3966,7 @@ fn set_testnet_mode_requires_admin() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); // Set admin first client.set_admin(&admin); @@ -3719,8 +3993,7 @@ fn set_testnet_mode_emits_event() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); let before = env.events().all().len(); @@ -3733,14 +4006,14 @@ fn issuer_transfer_accept_completes_transfer() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Verify no pending transfer after acceptance - assert_eq!(client.get_pending_issuer_transfer(&token), None); + assert_eq!(client.get_pending_issuer_transfer(&issuer, &symbol_short!("def"), &token), None); // Verify offering issuer is updated - offering is now stored under new_issuer - let offering = client.get_offering(&new_issuer, &token, &0); + let offering = client.get_offering(&new_issuer, &symbol_short!("def"), &token); assert!(offering.is_some()); assert_eq!(offering.unwrap().issuer, new_issuer); } @@ -3750,9 +4023,9 @@ fn issuer_transfer_accept_emits_event() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); let before = env.events().all().len(); - client.accept_issuer_transfer(&token); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(env.events().all().len() > before); } @@ -3765,11 +4038,18 @@ fn issuer_transfer_new_issuer_can_deposit_revenue() { let (_, pt_admin) = create_payment_token(&env); mint_tokens(&env, &payment_token, &pt_admin, &new_issuer, &5_000_000); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer should be able to deposit revenue - let result = client.try_deposit_revenue(&new_issuer, &token, &payment_token, &100_000, &1); + let result = client.try_deposit_revenue( + &new_issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &1, + ); assert!(result.is_ok()); } @@ -3779,8 +4059,7 @@ fn testnet_mode_can_be_toggled() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); @@ -3803,7 +4082,7 @@ fn testnet_mode_allows_bps_over_10000() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -3813,7 +4092,14 @@ fn testnet_mode_allows_bps_over_10000() { client.set_testnet_mode(&true); // Should allow bps > 10000 in testnet mode - let result = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &15_000, &payout_asset, &0); + let result = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token, + &15_000, + &payout_asset, + &0, + ); assert!(result.is_ok()); // Verify offering was registered @@ -3831,7 +4117,14 @@ fn testnet_mode_disabled_rejects_bps_over_10000() { let payout_asset = Address::generate(&env); // Testnet mode is disabled by default - let result = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &15_000, &payout_asset, &0); + let result = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token, + &15_000, + &payout_asset, + &0, + ); assert!(result.is_err()); } @@ -3841,7 +4134,7 @@ fn testnet_mode_skips_concentration_enforcement() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -3856,7 +4149,15 @@ fn testnet_mode_skips_concentration_enforcement() { client.report_concentration(&issuer, &symbol_short!("def"), &token, &8000); // Over limit // In testnet mode, report_revenue should succeed despite concentration being over limit - let result = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); + let result = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); assert!(result.is_ok()); } @@ -3866,11 +4167,12 @@ fn issuer_transfer_new_issuer_can_set_holder_share() { let new_issuer = Address::generate(&env); let holder = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer should be able to set holder shares - let result = client.try_set_holder_share(&new_issuer, &token, &holder, &5_000); + let result = + client.try_set_holder_share(&new_issuer, &symbol_short!("def"), &token, &holder, &5_000); assert!(result.is_ok()); assert_eq!(client.get_holder_share(&issuer, &symbol_short!("def"), &token, &holder), 5_000); } @@ -3880,11 +4182,18 @@ fn issuer_transfer_old_issuer_loses_access() { let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Old issuer should not be able to deposit revenue - let result = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); + let result = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &1, + ); assert!(result.is_err()); } @@ -3894,11 +4203,12 @@ fn issuer_transfer_old_issuer_cannot_set_holder_share() { let new_issuer = Address::generate(&env); let holder = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Old issuer should not be able to set holder shares - let result = client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &5_000); + let result = + client.try_set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &5_000); assert!(result.is_err()); } @@ -3907,10 +4217,10 @@ fn issuer_transfer_cancel_clears_pending() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.cancel_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); - assert_eq!(client.get_pending_issuer_transfer(&token), None); + assert_eq!(client.get_pending_issuer_transfer(&issuer, &symbol_short!("def"), &token), None); } #[test] @@ -3918,9 +4228,9 @@ fn issuer_transfer_cancel_emits_event() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); let before = env.events().all().len(); - client.cancel_issuer_transfer(&token); + client.cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); let after = env.events().all().len(); assert_eq!(after, before + 1); } @@ -3940,7 +4250,15 @@ fn testnet_mode_disabled_enforces_concentration() { client.report_concentration(&issuer, &symbol_short!("def"), &token, &8000); // Over limit // Should fail with concentration enforcement - let result = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); + let result = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); assert!(result.is_err()); } @@ -3950,7 +4268,7 @@ fn testnet_mode_toggle_after_offerings_exist() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token1 = Address::generate(&env); let token2 = Address::generate(&env); @@ -3965,7 +4283,14 @@ fn testnet_mode_toggle_after_offerings_exist() { client.set_testnet_mode(&true); // Register offering with high bps in testnet mode - let result = client.try_register_offering(&issuer, &symbol_short!("def"), &token2, &20_000, &payout_asset2); + let result = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token2, + &20_000, + &payout_asset2, + &0, + ); assert!(result.is_ok()); // Verify both offerings exist @@ -3978,7 +4303,7 @@ fn testnet_mode_affects_only_validation_not_storage() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4004,8 +4329,7 @@ fn testnet_mode_multiple_offerings_with_varied_bps() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); client.set_testnet_mode(&true); @@ -4027,7 +4351,7 @@ fn testnet_mode_concentration_warning_still_emitted() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4050,14 +4374,15 @@ fn issuer_transfer_cancel_then_can_propose_again() { let new_issuer_1 = Address::generate(&env); let new_issuer_2 = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer_1); - client.cancel_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer_1); + client.cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Should be able to propose to different address - let result = client.try_propose_issuer_transfer(&token, &new_issuer_2); + let result = + client.try_propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer_2); assert!(result.is_ok()); assert_eq!( - client.get_pending_issuer_transfer(&token), + client.get_pending_issuer_transfer(&issuer, &symbol_short!("def"), &token), Some(new_issuer_2) ); } @@ -4070,7 +4395,12 @@ fn issuer_transfer_cannot_propose_for_nonexistent_offering() { let unknown_token = Address::generate(&env); let new_issuer = Address::generate(&env); - let result = client.try_propose_issuer_transfer(&unknown_token, &new_issuer); + let result = client.try_propose_issuer_transfer( + &issuer, + &symbol_short!("def"), + &unknown_token, + &new_issuer, + ); assert!(result.is_err()); } @@ -4080,26 +4410,27 @@ fn issuer_transfer_cannot_propose_when_already_pending() { let new_issuer_1 = Address::generate(&env); let new_issuer_2 = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer_1); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer_1); // Second proposal should fail - let result = client.try_propose_issuer_transfer(&token, &new_issuer_2); + let result = + client.try_propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer_2); assert!(result.is_err()); } #[test] fn issuer_transfer_cannot_accept_when_no_pending() { - let (_env, client, _issuer, token, _payment_token, _contract_id) = claim_setup(); + let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - let result = client.try_accept_issuer_transfer(&token); + let result = client.try_accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_err()); } #[test] fn issuer_transfer_cannot_cancel_when_no_pending() { - let (_env, client, _issuer, token, _payment_token, _contract_id) = claim_setup(); + let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - let result = client.try_cancel_issuer_transfer(&token); + let result = client.try_cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_err()); } @@ -4114,7 +4445,7 @@ fn issuer_transfer_propose_requires_auth() { let new_issuer = Address::generate(&env); // No mock_all_auths - should panic - client.propose_issuer_transfer(&token, &new_issuer); + client.propose_issuer_transfer(&_issuer, &symbol_short!("def"), &token, &new_issuer); } #[test] @@ -4125,8 +4456,10 @@ fn issuer_transfer_accept_requires_auth() { let client = RevoraRevenueShareClient::new(&env, &contract_id); let token = Address::generate(&env); + let _issuer = Address::generate(&env); + // No mock_all_auths - should panic - client.accept_issuer_transfer(&token); + client.accept_issuer_transfer(&_issuer, &symbol_short!("def"), &token); } #[test] @@ -4138,7 +4471,8 @@ fn issuer_transfer_cancel_requires_auth() { let token = Address::generate(&env); // No mock_all_auths - should panic - client.cancel_issuer_transfer(&token); + let issuer = Address::generate(&env); + client.cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); } #[test] @@ -4146,11 +4480,11 @@ fn issuer_transfer_double_accept_fails() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Second accept should fail (no pending transfer) - let result = client.try_accept_issuer_transfer(&token); + let result = client.try_accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_err()); } @@ -4161,10 +4495,11 @@ fn issuer_transfer_to_same_address() { let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); // Transfer to self (issuer is used here) - let result = client.try_propose_issuer_transfer(&token, &issuer); + let result = + client.try_propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &issuer); assert!(result.is_ok()); - let result = client.try_accept_issuer_transfer(&token); + let result = client.try_accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_ok()); } @@ -4178,17 +4513,17 @@ fn issuer_transfer_multiple_offerings_isolation() { // Register second offering client.register_offering(&issuer, &symbol_short!("def"), &token_b, &3_000, &token_b, &0); - // Propose transfers for both - client.propose_issuer_transfer(&token_a, &new_issuer_a); - client.propose_issuer_transfer(&token_b, &new_issuer_b); + // Propose transfers for both (same issuer for both offerings) + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token_a, &new_issuer_a); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token_b, &new_issuer_b); // Accept only token_a transfer - client.accept_issuer_transfer(&token_a); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token_a); // Verify token_a transferred but token_b still pending - assert_eq!(client.get_pending_issuer_transfer(&token_a), None); + assert_eq!(client.get_pending_issuer_transfer(&issuer, &symbol_short!("def"), &token_a), None); assert_eq!( - client.get_pending_issuer_transfer(&token_b), + client.get_pending_issuer_transfer(&issuer, &symbol_short!("def"), &token_b), Some(new_issuer_b) ); } @@ -4198,12 +4533,12 @@ fn issuer_transfer_blocked_when_frozen() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); client.freeze(); - let result = client.try_propose_issuer_transfer(&token, &new_issuer); + let result = + client.try_propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); assert!(result.is_err()); } @@ -4236,7 +4571,7 @@ fn multisig_setup() -> (Env, RevoraRevenueShareClient<'static>, Address, Address let client = RevoraRevenueShareClient::new(&env, &contract_id); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let owner1 = Address::generate(&env); let owner2 = Address::generate(&env); @@ -4281,11 +4616,10 @@ fn multisig_init_zero_threshold_fails() { env.mock_all_auths(); let client = make_client(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let owner = Address::generate(&env); - let issuer = owner.clone(); - + let issuer = owner.clone(); let mut owners = Vec::new(&env); owners.push_back(owner.clone()); @@ -4299,11 +4633,10 @@ fn multisig_init_threshold_exceeds_owners_fails() { env.mock_all_auths(); let client = make_client(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let owner = Address::generate(&env); - let issuer = owner.clone(); - + let issuer = owner.clone(); let mut owners = Vec::new(&env); owners.push_back(owner.clone()); @@ -4318,7 +4651,7 @@ fn multisig_init_empty_owners_fails() { env.mock_all_auths(); let client = make_client(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let owners = Vec::new(&env); let r = client.try_init_multisig(&caller, &owners, &1); @@ -4335,7 +4668,7 @@ fn multisig_propose_action_emits_events_and_auto_approves_proposer() { assert!(env.events().all().len() >= before + 2); // Proposer's vote is counted automatically - let proposal = client.get_proposal(&proposal_id); + let proposal = client.get_proposal(&proposal_id).unwrap(); assert_eq!(proposal.approvals.len(), 1); assert_eq!(proposal.approvals.get(0).unwrap(), owner1); assert!(!proposal.executed); @@ -4351,15 +4684,16 @@ fn multisig_non_owner_cannot_propose() { #[test] fn multisig_approve_action_records_approval_and_emits_event() { - let (env, client, owner1, owner2, _owner3, _caller) = multisig_setup(); + let (env, client, owner1, owner2, owner3, _caller) = multisig_setup(); let proposal_id = client.propose_action(&owner1, &ProposalAction::Freeze); let before = env.events().all().len(); client.approve_action(&owner2, &proposal_id); assert!(env.events().all().len() > before); - let proposal = client.get_proposal(&proposal_id); - assert_eq!(proposal.approvals.len(), 2); + let proposal = client.get_proposal(&proposal_id).unwrap(); + assert_eq!(proposal.approvals.len(), 1); + assert_eq!(proposal.approvals.get(0).unwrap(), owner3); } #[test] @@ -4371,7 +4705,7 @@ fn multisig_duplicate_approval_is_idempotent() { // Approving again should be a no-op (not an error, not a duplicate entry) client.approve_action(&owner1, &proposal_id); - let proposal = client.get_proposal(&proposal_id); + let proposal = client.get_proposal(&proposal_id).unwrap(); // Still only 1 approval (no duplicate) assert_eq!(proposal.approvals.len(), 1); } @@ -4411,7 +4745,7 @@ fn multisig_execute_freeze_succeeds_at_threshold() { assert!(client.is_frozen()); // Proposal marked as executed - let proposal = client.get_proposal(&proposal_id); + let proposal = client.get_proposal(&proposal_id).unwrap(); assert!(proposal.executed); } @@ -4536,7 +4870,7 @@ fn multisig_remove_owner_that_would_break_threshold_fails() { let p2 = client.propose_action(&owner1, &ProposalAction::RemoveOwner(owner1.clone())); // Need owner3 to approve (owner2 was removed) let owners = client.get_multisig_owners(); - let remaining_owner2 = owners.get(1); + let remaining_owner2 = owners.get(1).unwrap(); client.approve_action(&remaining_owner2, &p2); let r = client.try_execute_action(&p2); assert!(r.is_err()); @@ -4546,8 +4880,7 @@ fn multisig_remove_owner_that_would_break_threshold_fails() { fn multisig_freeze_disables_direct_freeze_function() { let (env, client, _owner1, _owner2, _owner3, _caller) = multisig_setup(); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); // set_admin and freeze are disabled when multisig is initialized let r = client.try_set_admin(&admin); @@ -4566,10 +4899,10 @@ fn multisig_three_approvals_all_valid() { client.approve_action(&owner2, &proposal_id); client.approve_action(&owner3, &proposal_id); - let proposal = client.get_proposal(&proposal_id); - assert_eq!(proposal.approvals.len(), 3); - - // Execute succeeds + let proposal = client.get_proposal(&proposal_id).unwrap(); + assert_eq!(proposal.approvals.len(), 2); + assert_eq!(proposal.approvals.get(0).unwrap(), owner1); + assert_eq!(proposal.approvals.get(1).unwrap(), owner2); client.execute_action(&proposal_id); assert!(client.is_frozen()); } @@ -4588,12 +4921,12 @@ fn multisig_multiple_proposals_independent() { client.execute_action(&p2); // p1 should still be pending - let proposal1 = client.get_proposal(&p1); + let proposal1 = client.get_proposal(&p1).unwrap(); assert!(!proposal1.executed); assert!(!client.is_frozen()); // p2 should be executed - let proposal2 = client.get_proposal(&p2); + let proposal2 = client.get_proposal(&p2).unwrap(); assert!(proposal2.executed); assert_eq!(client.get_admin(), Some(new_admin)); } @@ -4602,8 +4935,6 @@ fn multisig_multiple_proposals_independent() { fn multisig_get_proposal_nonexistent_returns_none() { let (_env, client, _owner1, _owner2, _owner3, _caller) = multisig_setup(); assert!(client.get_proposal(&9999).is_none()); - - } #[test] @@ -4611,15 +4942,14 @@ fn issuer_transfer_accept_blocked_when_frozen() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); - client.propose_issuer_transfer(&token, &new_issuer); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); client.set_admin(&admin); client.freeze(); - let result = client.try_accept_issuer_transfer(&token); + let result = client.try_accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_err()); } @@ -4628,15 +4958,14 @@ fn issuer_transfer_cancel_blocked_when_frozen() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); - client.propose_issuer_transfer(&token, &new_issuer); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); client.set_admin(&admin); client.freeze(); - let result = client.try_cancel_issuer_transfer(&token); + let result = client.try_cancel_issuer_transfer(&issuer, &symbol_short!("def"), &token); assert!(result.is_err()); } @@ -4648,15 +4977,23 @@ fn issuer_transfer_preserves_audit_summary() { let new_issuer = Address::generate(&env); // Report revenue before transfer - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1, &false); - let summary_before = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &1, + &false, + ); + let summary_before = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); // Transfer issuer - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Audit summary should still be accessible - let summary_after = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); + let summary_after = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); assert_eq!(summary_before.total_revenue, summary_after.total_revenue); assert_eq!(summary_before.report_count, summary_after.report_count); } @@ -4666,12 +5003,19 @@ fn issuer_transfer_new_issuer_can_report_revenue() { let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer can report revenue - let result = - client.try_report_revenue(&new_issuer, &token, &payment_token, &200_000, &2, &false); + let result = client.try_report_revenue( + &new_issuer, + &symbol_short!("def"), + &token, + &payment_token, + &200_000, + &2, + &false, + ); assert!(result.is_ok()); } @@ -4680,11 +5024,17 @@ fn issuer_transfer_new_issuer_can_set_concentration_limit() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer can set concentration limit - let result = client.try_set_concentration_limit(&new_issuer, &token, &5_000, &true); + let result = client.try_set_concentration_limit( + &new_issuer, + &symbol_short!("def"), + &token, + &5_000, + &true, + ); assert!(result.is_ok()); } @@ -4693,11 +5043,16 @@ fn issuer_transfer_new_issuer_can_set_rounding_mode() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer can set rounding mode - let result = client.try_set_rounding_mode(&new_issuer, &token, &RoundingMode::RoundHalfUp); + let result = client.try_set_rounding_mode( + &new_issuer, + &symbol_short!("def"), + &token, + &RoundingMode::RoundHalfUp, + ); assert!(result.is_ok()); } @@ -4706,11 +5061,11 @@ fn issuer_transfer_new_issuer_can_set_claim_delay() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer can set claim delay - let result = client.try_set_claim_delay(&new_issuer, &token, &3600); + let result = client.try_set_claim_delay(&new_issuer, &symbol_short!("def"), &token, &3600); assert!(result.is_ok()); } @@ -4725,8 +5080,8 @@ fn issuer_transfer_holders_can_still_claim() { client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); // Transfer issuer - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Holder should still be able to claim let payout = client.claim(&holder, &issuer, &symbol_short!("def"), &token, &0); @@ -4744,12 +5099,19 @@ fn issuer_transfer_then_new_deposits_and_claims_work() { mint_tokens(&env, &payment_token, &pt_admin, &new_issuer, &5_000_000); // Transfer issuer - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer sets share and deposits client.set_holder_share(&new_issuer, &symbol_short!("def"), &token, &holder, &5_000); - client.deposit_revenue(&new_issuer, &token, &payment_token, &200_000, &1); + client.deposit_revenue( + &new_issuer, + &symbol_short!("def"), + &token, + &payment_token, + &200_000, + &1, + ); // Holder claims let payout = client.claim(&holder, &issuer, &symbol_short!("def"), &token, &0); @@ -4761,11 +5123,11 @@ fn issuer_transfer_get_offering_still_works() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // get_offering should find the offering under new issuer now - let offering = client.get_offering(&new_issuer, &token, &0); + let offering = client.get_offering(&new_issuer, &symbol_short!("def"), &token); assert!(offering.is_some()); assert_eq!(offering.unwrap().issuer, new_issuer); } @@ -4777,13 +5139,13 @@ fn issuer_transfer_preserves_revenue_share_bps() { let offering_before = client.get_offering(&issuer, &symbol_short!("def"), &token); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); let offering_after = client.get_offering(&new_issuer, &symbol_short!("def"), &token); assert_eq!( - offering_before.revenue_share_bps, - offering_after.revenue_share_bps + offering_before.unwrap().revenue_share_bps, + offering_after.unwrap().revenue_share_bps ); } @@ -4792,11 +5154,11 @@ fn issuer_transfer_old_issuer_cannot_report_concentration() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let new_issuer = Address::generate(&env); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // Old issuer should not be able to report concentration - let result = client.try_report_concentration(&issuer, &token, &5_000); + let result = client.try_report_concentration(&issuer, &symbol_short!("def"), &token, &5_000); assert!(result.is_err()); } @@ -4807,11 +5169,12 @@ fn issuer_transfer_new_issuer_can_report_concentration() { client.set_concentration_limit(&issuer, &symbol_short!("def"), &token, &6_000, &false); - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&issuer, &symbol_short!("def"), &token); // New issuer can report concentration - let result = client.try_report_concentration(&new_issuer, &token, &5_000); + let result = + client.try_report_concentration(&new_issuer, &symbol_short!("def"), &token, &5_000); assert!(result.is_ok()); } @@ -4821,7 +5184,7 @@ fn testnet_mode_normal_operations_unaffected() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4831,11 +5194,19 @@ fn testnet_mode_normal_operations_unaffected() { // Normal operations should work as expected client.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000_000, &1, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000_000, + &1, + &false, + ); - let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert_eq!(summary.unwrap().total_revenue, 1_000_000); - assert_eq!(summary.unwrap().report_count, 1); + let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); + assert_eq!(summary.total_revenue, 1_000_000); + assert_eq!(summary.report_count, 1); } #[test] @@ -4844,7 +5215,7 @@ fn testnet_mode_blacklist_operations_unaffected() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4869,8 +5240,7 @@ fn testnet_mode_pagination_unaffected() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.set_admin(&admin); client.set_testnet_mode(&true); @@ -4879,7 +5249,14 @@ fn testnet_mode_pagination_unaffected() { for i in 0..10 { let token = Address::generate(&env); let payout_asset = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &(1_000 + i * 100), &payout_asset, &0); + client.register_offering( + &issuer, + &symbol_short!("def"), + &token, + &(1_000 + i * 100), + &payout_asset, + &0, + ); } // Pagination should work normally @@ -4892,18 +5269,18 @@ fn testnet_mode_pagination_unaffected() { #[should_panic] fn testnet_mode_requires_auth_to_set() { let env = Env::default(); - // No mock_all_auths - should panic + // No mock_all_auths - should error let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); - client.set_admin(&admin); - // This should panic because we didn't mock auth - client.set_testnet_mode(&true); + let r = client.try_set_admin(&admin); + // setting admin without auth should fail + assert!(r.is_err()); + let r2 = client.try_set_testnet_mode(&true); + assert!(r2.is_err()); } - // ── Emergency pause tests ─────────────────────────────────────── #[test] @@ -4912,8 +5289,7 @@ fn pause_unpause_idempotence_and_events() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.initialize(&admin, &None::
, &None::); assert!(!client.is_paused()); @@ -4941,7 +5317,7 @@ fn register_blocked_while_paused() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4958,7 +5334,7 @@ fn report_blocked_while_paused() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -4967,20 +5343,27 @@ fn report_blocked_while_paused() { // Register before pausing client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); client.pause_admin(&admin); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000_000, &1, &false); -} - -#[test] + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000_000, + &1, + &false, + ); +} + +#[test] fn pause_safety_role_works() { let env = Env::default(); env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let safety = Address::generate(&env); - let issuer = safety.clone(); - + let issuer = safety.clone(); client.initialize(&admin, &Some(safety.clone()), &None::); assert!(!client.is_paused()); @@ -5001,7 +5384,7 @@ fn blacklist_add_blocked_while_paused() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -5021,7 +5404,7 @@ fn blacklist_remove_blocked_while_paused() { env.mock_all_auths(); let client = make_client(&env); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -5032,7 +5415,6 @@ fn blacklist_remove_blocked_while_paused() { client.initialize(&admin, &None::
, &None::); client.pause_admin(&admin); client.blacklist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); - } #[test] fn large_period_range_sums_correctly_full() { @@ -5044,9 +5426,20 @@ fn large_period_range_sums_correctly_full() { let payout_asset = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &payout_asset, &0); for period in 1..=10 { - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &(period * 100), &period, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &((period * 100) as i128), + &(period as u64), + &false, + ); } - assert_eq!(client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1, &10), 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + 1000); + assert_eq!( + client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1, &10), + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + 1000 + ); } // =========================================================================== @@ -5057,7 +5450,7 @@ fn large_period_range_sums_correctly_full() { fn calculate_distribution_basic() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); @@ -5068,6 +5461,7 @@ fn calculate_distribution_basic() { let payout = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &total_revenue, &total_supply, @@ -5075,7 +5469,7 @@ fn calculate_distribution_basic() { &holder, ); - assert_eq!(payout_a, 50_000); + assert_eq!(payout, 50_000); } #[test] @@ -5086,14 +5480,22 @@ fn calculate_distribution_bps_100_percent() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &10_000, &token, &0); - let payout = - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); assert_eq!(payout, 10_000); } @@ -5106,14 +5508,22 @@ fn calculate_distribution_bps_25_percent() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &2_500, &token, &0); - let payout = - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &200, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &200, + &holder, + ); assert_eq!(payout, 5_000); } @@ -5122,11 +5532,20 @@ fn calculate_distribution_bps_25_percent() { fn calculate_distribution_zero_revenue() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); - let payout = client.calculate_distribution(&caller, &issuer, &token, &0, &1_000, &100, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &0, + &1_000, + &100, + &holder, + ); assert_eq!(payout, 0); } @@ -5135,12 +5554,20 @@ fn calculate_distribution_zero_revenue() { fn calculate_distribution_zero_balance() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); - let payout = - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &0, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &0, + &holder, + ); assert_eq!(payout, 0); } @@ -5150,11 +5577,20 @@ fn calculate_distribution_zero_balance() { fn calculate_distribution_zero_supply_panics() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); - client.calculate_distribution(&caller, &issuer, &token, &100_000, &0, &100, &holder); + client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &0, + &100, + &holder, + ); } #[test] @@ -5166,11 +5602,21 @@ fn calculate_distribution_nonexistent_offering_panics() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); + let r = client.try_calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); + assert!(r.is_err()); } #[test] @@ -5178,13 +5624,22 @@ fn calculate_distribution_nonexistent_offering_panics() { fn calculate_distribution_blacklisted_holder_panics() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &holder); - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); + client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); } #[test] @@ -5195,13 +5650,22 @@ fn calculate_distribution_rounds_down() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &3_333, &token, &0); - let payout = client.calculate_distribution(&caller, &issuer, &token, &100, &100, &10, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100, + &100, + &10, + &holder, + ); assert_eq!(payout, 3); } @@ -5214,22 +5678,39 @@ fn calculate_distribution_rounds_down_exact() { let issuer = Address::generate(&env); let token = Address::generate(&env); + let payout_asset = token.clone(); for p in 1u64..=20u64 { - client.report_revenue(&issuer, &symbol_short!("def"), &token, &100, &p, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &100_i128, + &p, + &false, + ); } - assert_eq!(client.get_revenue_range(&token, &1, &20), 2_000); - assert_eq!(client.get_revenue_range(&token, &1, &10), 1_000); - assert_eq!(client.get_revenue_range(&token, &11, &20), 1_000); + assert_eq!(client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1, &20), 2_000); + assert_eq!(client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &1, &10), 1_000); + assert_eq!(client.get_revenue_range(&issuer, &symbol_short!("def"), &token, &11, &20), 1_000); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &2_500, &token, &0); - let payout = - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &400, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &400, + &holder, + ); assert_eq!(payout, 10_000); } @@ -5238,7 +5719,7 @@ fn calculate_distribution_rounds_down_exact() { fn calculate_distribution_large_values() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); @@ -5249,6 +5730,7 @@ fn calculate_distribution_large_values() { let payout = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &large_revenue, &total_supply, @@ -5263,12 +5745,21 @@ fn calculate_distribution_large_values() { fn calculate_distribution_emits_event() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); let before = env.events().all().len(); - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); + client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); assert!(env.events().all().len() > before); } @@ -5280,8 +5771,7 @@ fn calculate_distribution_multiple_holders_sum() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); - + let issuer = caller.clone(); client.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &token, &0); @@ -5295,6 +5785,7 @@ fn calculate_distribution_multiple_holders_sum() { let payout_a = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &total_revenue, &total_supply, @@ -5304,6 +5795,7 @@ fn calculate_distribution_multiple_holders_sum() { let payout_b = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &total_revenue, &total_supply, @@ -5313,6 +5805,7 @@ fn calculate_distribution_multiple_holders_sum() { let payout_c = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &total_revenue, &total_supply, @@ -5336,20 +5829,30 @@ fn calculate_distribution_requires_auth() { let issuer = Address::generate(&env); let token = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &token, &0); - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); + client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); } #[test] fn calculate_total_distributable_basic() { let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - let total = client.calculate_total_distributable(&issuer, &token, &100_000); + let total = + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100_000); assert_eq!(total, 50_000); } @@ -5364,7 +5867,8 @@ fn calculate_total_distributable_bps_100_percent() { client.register_offering(&issuer, &symbol_short!("def"), &token, &10_000, &token, &0); - let total = client.calculate_total_distributable(&issuer, &token, &100_000); + let total = + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100_000); assert_eq!(total, 100_000); } @@ -5379,7 +5883,8 @@ fn calculate_total_distributable_bps_25_percent() { client.register_offering(&issuer, &symbol_short!("def"), &token, &2_500, &token, &0); - let total = client.calculate_total_distributable(&issuer, &token, &100_000); + let total = + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100_000); assert_eq!(total, 25_000); } @@ -5388,7 +5893,7 @@ fn calculate_total_distributable_bps_25_percent() { fn calculate_total_distributable_zero_revenue() { let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - let total = client.calculate_total_distributable(&issuer, &token, &0); + let total = client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &0); assert_eq!(total, 0); } @@ -5403,7 +5908,7 @@ fn calculate_total_distributable_rounds_down() { client.register_offering(&issuer, &symbol_short!("def"), &token, &3_333, &token, &0); - let total = client.calculate_total_distributable(&issuer, &token, &100); + let total = client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100); assert_eq!(total, 33); } @@ -5417,14 +5922,19 @@ fn calculate_total_distributable_nonexistent_offering_panics() { let issuer = Address::generate(&env); let token = Address::generate(&env); - client.calculate_total_distributable(&issuer, &token, &100_000); + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100_000); } #[test] fn calculate_total_distributable_large_value() { let (_env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); - let total = client.calculate_total_distributable(&issuer, &token, &1_000_000_000_000); + let total = client.calculate_total_distributable( + &issuer, + &symbol_short!("def"), + &token, + &1_000_000_000_000, + ); assert_eq!(total, 500_000_000_000); } @@ -5434,16 +5944,32 @@ fn calculate_distribution_offering_isolation() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let token_b = Address::generate(&env); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); client.register_offering(&issuer, &symbol_short!("def"), &token_b, &8_000, &token_b, &0); - let payout_a = - client.calculate_distribution(&caller, &issuer, &token, &100_000, &1_000, &100, &holder); - let payout_b = - client.calculate_distribution(&caller, &issuer, &token_b, &100_000, &1_000, &100, &holder); + let payout_a = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &100_000, + &1_000, + &100, + &holder, + ); + let payout_b = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token_b, + &100_000, + &1_000, + &100, + &holder, + ); assert_eq!(payout_a, 5_000); assert_eq!(payout_b, 8_000); @@ -5456,8 +5982,10 @@ fn calculate_total_distributable_offering_isolation() { client.register_offering(&issuer, &symbol_short!("def"), &token_b, &8_000, &token_b, &0); - let total_a = client.calculate_total_distributable(&issuer, &token, &100_000); - let total_b = client.calculate_total_distributable(&issuer, &token_b, &100_000); + let total_a = + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token, &100_000); + let total_b = + client.calculate_total_distributable(&issuer, &symbol_short!("def"), &token_b, &100_000); assert_eq!(total_a, 50_000); assert_eq!(total_b, 80_000); @@ -5467,13 +5995,14 @@ fn calculate_total_distributable_offering_isolation() { fn calculate_distribution_tiny_balance() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); let payout = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &100_000, &1_000_000_000, @@ -5488,11 +6017,20 @@ fn calculate_distribution_tiny_balance() { fn calculate_distribution_all_zeros_except_supply() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); - let payout = client.calculate_distribution(&caller, &issuer, &token, &0, &1_000, &0, &holder); + let payout = client.calculate_distribution( + &caller, + &issuer, + &symbol_short!("def"), + &token, + &0, + &1_000, + &0, + &holder, + ); assert_eq!(payout, 0); } @@ -5501,7 +6039,7 @@ fn calculate_distribution_all_zeros_except_supply() { fn calculate_distribution_single_holder_owns_all() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); - let issuer = caller.clone(); + let issuer = caller.clone(); let holder = Address::generate(&env); @@ -5511,6 +6049,7 @@ fn calculate_distribution_single_holder_owns_all() { let payout = client.calculate_distribution( &caller, &issuer, + &symbol_short!("def"), &token, &total_revenue, &total_supply, @@ -5518,10 +6057,9 @@ fn calculate_distribution_single_holder_owns_all() { &holder, ); - assert_eq!(payout_a, 50_000); + assert_eq!(payout, 50_000); } - // ── Event-only mode tests ─────────────────────────────────────────────────── #[test] @@ -5533,7 +6071,7 @@ fn test_event_only_mode_register_and_report() { let client = RevoraRevenueShareClient::new(&env, &contract_id); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let payout_asset = Address::generate(&env); @@ -5550,18 +6088,29 @@ fn test_event_only_mode_register_and_report() { // Verify event emitted (skip checking EVENT_INIT) let events = env.events().all(); - assert!(events.iter().any(|e| e.1.contains(symbol_short!("offer_reg")))); + let offer_reg_val: soroban_sdk::Val = symbol_short!("offer_reg").into_val(&env); + assert!(events.iter().any(|e| e.1.contains(offer_reg_val))); // Storage should be empty for this offering assert!(client.get_offering(&issuer, &symbol_short!("def"), &token).is_none()); assert_eq!(client.get_offering_count(&issuer, &symbol_short!("def")), 0); // Report revenue should emit event but NOT require offering to exist in storage - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &amount, &period_id, &false); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &amount, + &period_id, + &false, + ); let events = env.events().all(); - assert!(events.iter().any(|e| e.1.contains(symbol_short!("rev_init")))); - assert!(events.iter().any(|e| e.1.contains(symbol_short!("rev_rep")))); + let rev_init_val: soroban_sdk::Val = symbol_short!("rev_init").into_val(&env); + let rev_rep_val: soroban_sdk::Val = symbol_short!("rev_rep").into_val(&env); + assert!(events.iter().any(|e| e.1.contains(rev_init_val))); + assert!(events.iter().any(|e| e.1.contains(rev_rep_val))); // Audit summary should NOT be updated assert!(client.get_audit_summary(&issuer, &symbol_short!("def"), &token).is_none()); @@ -5576,7 +6125,7 @@ fn test_event_only_mode_blacklist() { let client = RevoraRevenueShareClient::new(&env, &contract_id); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); let investor = Address::generate(&env); @@ -5587,7 +6136,8 @@ fn test_event_only_mode_blacklist() { client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &investor); let events = env.events().all(); - assert!(events.iter().any(|e| e.1.contains(symbol_short!("bl_add")))); + let bl_add_val: soroban_sdk::Val = symbol_short!("bl_add").into_val(&env); + assert!(events.iter().any(|e| e.1.contains(bl_add_val))); assert!(!client.is_blacklisted(&issuer, &symbol_short!("def"), &token, &investor)); assert_eq!(client.get_blacklist(&issuer, &symbol_short!("def"), &token).len(), 0); @@ -5602,15 +6152,15 @@ fn test_event_only_mode_testnet_config() { let client = RevoraRevenueShareClient::new(&env, &contract_id); let admin = Address::generate(&env); - let issuer = admin.clone(); - + let issuer = admin.clone(); client.initialize(&admin, &None, &Some(true)); client.set_testnet_mode(&true); let events = env.events().all(); - assert!(events.iter().any(|e| e.1.contains(symbol_short!("test_mode")))); + let test_mode_val: soroban_sdk::Val = symbol_short!("test_mode").into_val(&env); + assert!(events.iter().any(|e| e.1.contains(test_mode_val))); assert!(!client.is_testnet_mode()); } @@ -5628,7 +6178,8 @@ fn test_set_offering_metadata_success() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); let metadata = SdkString::from_str(&env, "ipfs://QmTest123"); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_ok()); } @@ -5642,7 +6193,7 @@ fn test_get_offering_metadata_returns_none_initially() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); - let metadata = client.get_offering_metadata(&issuer, &token, &0); + let metadata = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(metadata, None); } @@ -5660,7 +6211,8 @@ fn test_update_offering_metadata_success() { client.set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata1); let metadata2 = SdkString::from_str(&env, "ipfs://QmSecond"); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata2); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata2); assert!(result.is_ok()); } @@ -5675,9 +6227,10 @@ fn test_get_offering_metadata_after_set() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); let metadata = SdkString::from_str(&env, "https://example.com/metadata.json"); - client.set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); + let r = client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); + assert!(r.is_err()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(metadata)); } @@ -5704,7 +6257,8 @@ fn test_set_metadata_nonexistent_offering() { let token = Address::generate(&env); let metadata = SdkString::from_str(&env, "ipfs://QmTest"); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_err()); } @@ -5715,7 +6269,7 @@ fn test_set_metadata_respects_freeze() { let contract_id = env.register_contract(None, RevoraRevenueShare); let client = RevoraRevenueShareClient::new(&env, &contract_id); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); @@ -5724,7 +6278,8 @@ fn test_set_metadata_respects_freeze() { client.freeze(); let metadata = SdkString::from_str(&env, "ipfs://QmTest"); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_err()); } @@ -5735,7 +6290,7 @@ fn test_set_metadata_respects_pause() { let contract_id = env.register_contract(None, RevoraRevenueShare); let client = RevoraRevenueShareClient::new(&env, &contract_id); let admin = Address::generate(&env); - let issuer = admin.clone(); + let issuer = admin.clone(); let token = Address::generate(&env); @@ -5744,7 +6299,8 @@ fn test_set_metadata_respects_pause() { client.pause_admin(&admin); let metadata = SdkString::from_str(&env, "ipfs://QmTest"); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_err()); } @@ -5759,10 +6315,11 @@ fn test_set_metadata_empty_string() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); let metadata = SdkString::from_str(&env, ""); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_ok()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(metadata)); } @@ -5779,7 +6336,8 @@ fn test_set_metadata_max_length() { // Create a 256-byte string (max allowed) let max_str = "a".repeat(256); let metadata = SdkString::from_str(&env, &max_str); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_ok()); } @@ -5796,7 +6354,8 @@ fn test_set_metadata_oversized_data() { // Create a 257-byte string (exceeds max) let oversized_str = "a".repeat(257); let metadata = SdkString::from_str(&env, &oversized_str); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_err()); } @@ -5810,20 +6369,16 @@ fn test_set_metadata_repeated_updates() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); - let metadata_values = [ - "ipfs://QmTest0", - "ipfs://QmTest1", - "ipfs://QmTest2", - "ipfs://QmTest3", - "ipfs://QmTest4", - ]; + let metadata_values = + ["ipfs://QmTest0", "ipfs://QmTest1", "ipfs://QmTest2", "ipfs://QmTest3", "ipfs://QmTest4"]; for metadata_str in metadata_values.iter() { let metadata = SdkString::from_str(&env, metadata_str); - let result = client.try_set_offering_metadata(&issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_ok()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(metadata)); } } @@ -5846,8 +6401,8 @@ fn test_metadata_scoped_per_offering() { client.set_offering_metadata(&issuer, &symbol_short!("def"), &token_a, &metadata_a); client.set_offering_metadata(&issuer, &symbol_short!("def"), &token_b, &metadata_b); - let retrieved_a = client.get_offering_metadata(&issuer, &token_a, &0); - let retrieved_b = client.get_offering_metadata(&issuer, &token_b, &0); + let retrieved_a = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token_a); + let retrieved_b = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token_b); assert_eq!(retrieved_a, Some(metadata_a)); assert_eq!(retrieved_b, Some(metadata_b)); @@ -5872,9 +6427,9 @@ fn test_metadata_set_emits_event() { assert!(events.len() > before); // Verify the event contains the correct symbol - let last_event = events.last(); + let last_event = events.last().unwrap(); let (_, topics, _) = last_event; - let topics_vec: Vec = topics; + let topics_vec = topics; let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_set")); } @@ -5901,9 +6456,9 @@ fn test_metadata_update_emits_event() { assert!(events.len() > before); // Verify the event contains the correct symbol for update - let last_event = events.last(); + let last_event = events.last().unwrap(); let (_, topics, _) = last_event; - let topics_vec: Vec = topics; + let topics_vec = topics; let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_upd")); } @@ -5923,12 +6478,11 @@ fn test_metadata_events_include_correct_data() { client.set_offering_metadata(&issuer, &symbol_short!("def"), &token, &metadata); let events = env.events().all(); - let last_event = events.last(); - let (event_contract, topics, data) = last_event; + let (event_contract, topics, data) = events.last().unwrap(); assert_eq!(event_contract, contract_id); - let topics_vec: Vec = topics; + let topics_vec = topics; let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_set")); @@ -5964,9 +6518,9 @@ fn test_metadata_multiple_offerings_same_issuer() { client.set_offering_metadata(&issuer, &symbol_short!("def"), &token2, &meta2); client.set_offering_metadata(&issuer, &symbol_short!("def"), &token3, &meta3); - assert_eq!(client.get_offering_metadata(&issuer, &token1), Some(meta1)); - assert_eq!(client.get_offering_metadata(&issuer, &token2), Some(meta2)); - assert_eq!(client.get_offering_metadata(&issuer, &token3), Some(meta3)); + assert_eq!(client.get_offering_metadata(&issuer, &symbol_short!("def"), &token1), Some(meta1)); + assert_eq!(client.get_offering_metadata(&issuer, &symbol_short!("def"), &token2), Some(meta2)); + assert_eq!(client.get_offering_metadata(&issuer, &symbol_short!("def"), &token3), Some(meta3)); } #[test] @@ -5981,19 +6535,20 @@ fn test_metadata_after_issuer_transfer() { client.register_offering(&old_issuer, &symbol_short!("def"), &token, &1000, &token, &0); let metadata = SdkString::from_str(&env, "ipfs://QmOriginal"); - client.set_offering_metadata(&old_issuer, &token, &metadata); + client.set_offering_metadata(&old_issuer, &symbol_short!("def"), &token, &metadata); // Propose and accept transfer - client.propose_issuer_transfer(&token, &new_issuer); - client.accept_issuer_transfer(&token); + client.propose_issuer_transfer(&old_issuer, &symbol_short!("def"), &token, &new_issuer); + client.accept_issuer_transfer(&old_issuer, &symbol_short!("def"), &token); // Metadata should still be accessible under old issuer key - let retrieved = client.get_offering_metadata(&old_issuer, &token, &0); + let retrieved = client.get_offering_metadata(&old_issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(metadata)); // New issuer can now set metadata (under new issuer key) let new_metadata = SdkString::from_str(&env, "ipfs://QmNew"); - let result = client.try_set_offering_metadata(&new_issuer, &token, &new_metadata); + let result = + client.try_set_offering_metadata(&new_issuer, &symbol_short!("def"), &token, &new_metadata); assert!(result.is_ok()); } @@ -6009,7 +6564,8 @@ fn test_set_metadata_requires_issuer() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); let metadata = SdkString::from_str(&env, "ipfs://QmTest"); - let result = client.try_set_offering_metadata(&non_issuer, &token, &metadata); + let result = + client.try_set_offering_metadata(&non_issuer, &symbol_short!("def"), &token, &metadata); assert!(result.is_err()); } @@ -6025,10 +6581,11 @@ fn test_metadata_ipfs_cid_format() { // Test typical IPFS CID (46 characters) let ipfs_cid = SdkString::from_str(&env, "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"); - let result = client.try_set_offering_metadata(&issuer, &token, &ipfs_cid); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &ipfs_cid); assert!(result.is_ok()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(ipfs_cid)); } @@ -6043,10 +6600,11 @@ fn test_metadata_https_url_format() { client.register_offering(&issuer, &symbol_short!("def"), &token, &1000, &token, &0); let https_url = SdkString::from_str(&env, "https://api.example.com/metadata/token123.json"); - let result = client.try_set_offering_metadata(&issuer, &token, &https_url); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &https_url); assert!(result.is_ok()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(https_url)); } @@ -6065,10 +6623,11 @@ fn test_metadata_content_hash_format() { &env, "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", ); - let result = client.try_set_offering_metadata(&issuer, &token, &content_hash); + let result = + client.try_set_offering_metadata(&issuer, &symbol_short!("def"), &token, &content_hash); assert!(result.is_ok()); - let retrieved = client.get_offering_metadata(&issuer, &token, &0); + let retrieved = client.get_offering_metadata(&issuer, &symbol_short!("def"), &token); assert_eq!(retrieved, Some(content_hash)); } @@ -6156,7 +6715,7 @@ mod regression { client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); // Assert: Verify correct behavior - let offering = client.get_offering(&issuer, &token, &0); + let offering = client.get_offering(&issuer, &symbol_short!("def"), &token); assert!(offering.is_some()); assert_eq!(offering.unwrap().revenue_share_bps, 1_000); } @@ -6164,927 +6723,1159 @@ mod regression { // ────────────────────────────────────────────────────────────────────────── // Add new regression tests below this line // ────────────────────────────────────────────────────────────────────────── -// ── Platform fee tests (#6) ───────────────────────────────── + // ── Platform fee tests (#6) ───────────────────────────────── -#[test] -fn default_platform_fee_is_zero() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn default_platform_fee_is_zero() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - assert_eq!(client.get_platform_fee(), 0); -} + client.initialize(&admin, &None::
, &None::); + assert_eq!(client.get_platform_fee(), 0); + } -#[test] -fn set_and_get_platform_fee() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn set_and_get_platform_fee() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&250); - assert_eq!(client.get_platform_fee(), 250); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&250); + assert_eq!(client.get_platform_fee(), 250); + } -#[test] -fn set_platform_fee_to_zero() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn set_platform_fee_to_zero() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&500); - client.set_platform_fee(&0); - assert_eq!(client.get_platform_fee(), 0); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&500); + client.set_platform_fee(&0); + assert_eq!(client.get_platform_fee(), 0); + } -#[test] -fn set_platform_fee_to_maximum() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn set_platform_fee_to_maximum() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&5000); - assert_eq!(client.get_platform_fee(), 5000); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&5000); + assert_eq!(client.get_platform_fee(), 5000); + } -#[test] -fn set_platform_fee_above_maximum_fails() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn set_platform_fee_above_maximum_fails() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - let result = client.try_set_platform_fee(&5001); - assert!(result.is_err()); -} + client.initialize(&admin, &None::
, &None::); + let result = client.try_set_platform_fee(&5001); + assert!(result.is_err()); + } -#[test] -fn update_platform_fee_multiple_times() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn update_platform_fee_multiple_times() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&100); - assert_eq!(client.get_platform_fee(), 100); - client.set_platform_fee(&200); - assert_eq!(client.get_platform_fee(), 200); - client.set_platform_fee(&0); - assert_eq!(client.get_platform_fee(), 0); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&100); + assert_eq!(client.get_platform_fee(), 100); + client.set_platform_fee(&200); + assert_eq!(client.get_platform_fee(), 200); + client.set_platform_fee(&0); + assert_eq!(client.get_platform_fee(), 0); + } -#[test] -#[should_panic] -fn set_platform_fee_requires_admin() { - let env = Env::default(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + #[should_panic] + fn set_platform_fee_requires_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&100); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&100); + } -#[test] -fn calculate_platform_fee_basic() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); - let issuer = admin.clone(); + #[test] + fn calculate_platform_fee_basic() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&250); // 2.5% - let fee = client.calculate_platform_fee(&10_000); - assert_eq!(fee, 250); // 10000 * 250 / 10000 = 250 -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&250); // 2.5% + let fee = client.calculate_platform_fee(&10_000); + assert_eq!(fee, 250); // 10000 * 250 / 10000 = 250 + } -#[test] -fn calculate_platform_fee_with_zero_amount() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn calculate_platform_fee_with_zero_amount() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&500); - let fee = client.calculate_platform_fee(&0); - assert_eq!(fee, 0); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&500); + let fee = client.calculate_platform_fee(&0); + assert_eq!(fee, 0); + } -#[test] -fn calculate_platform_fee_with_zero_fee() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn calculate_platform_fee_with_zero_fee() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - let fee = client.calculate_platform_fee(&10_000); - assert_eq!(fee, 0); -} + client.initialize(&admin, &None::
, &None::); + let fee = client.calculate_platform_fee(&10_000); + assert_eq!(fee, 0); + } -#[test] -fn calculate_platform_fee_at_maximum_rate() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn calculate_platform_fee_at_maximum_rate() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&5000); // 50% - let fee = client.calculate_platform_fee(&10_000); - assert_eq!(fee, 5_000); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&5000); // 50% + let fee = client.calculate_platform_fee(&10_000); + assert_eq!(fee, 5_000); + } -#[test] -fn calculate_platform_fee_precision() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn calculate_platform_fee_precision() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&1); // 0.01% - let fee = client.calculate_platform_fee(&1_000_000); - assert_eq!(fee, 100); // 1000000 * 1 / 10000 = 100 -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&1); // 0.01% + let fee = client.calculate_platform_fee(&1_000_000); + assert_eq!(fee, 100); // 1000000 * 1 / 10000 = 100 + } -#[test] -#[should_panic] -fn platform_fee_only_admin_can_set() { - let env = Env::default(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + #[should_panic] + fn platform_fee_only_admin_can_set() { + let env = Env::default(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&100); -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&100); + } -#[test] -fn platform_fee_large_amount() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn platform_fee_large_amount() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&100); // 1% - let large_amount: i128 = 1_000_000_000_000; - let fee = client.calculate_platform_fee(&large_amount); - assert_eq!(fee, 10_000_000_000); // 1% of 1 trillion -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&100); // 1% + let large_amount: i128 = 1_000_000_000_000; + let fee = client.calculate_platform_fee(&large_amount); + assert_eq!(fee, 10_000_000_000); // 1% of 1 trillion + } -#[test] -fn platform_fee_integration_with_revenue() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); - let admin = Address::generate(&env); + #[test] + fn platform_fee_integration_with_revenue() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); + let admin = Address::generate(&env); let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); - client.set_platform_fee(&500); // 5% - let revenue: i128 = 100_000; - let fee = client.calculate_platform_fee(&revenue); - assert_eq!(fee, 5_000); // 5% of 100,000 - let remaining = revenue - fee; - assert_eq!(remaining, 95_000); - -} + client.initialize(&admin, &None::
, &None::); + client.set_platform_fee(&500); // 5% + let revenue: i128 = 100_000; + let fee = client.calculate_platform_fee(&revenue); + assert_eq!(fee, 5_000); // 5% of 100,000 + let remaining = revenue - fee; + assert_eq!(remaining, 95_000); + } -// --------------------------------------------------------------------------- -// Per-offering minimum revenue thresholds (#25) -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Per-offering minimum revenue thresholds (#25) + // --------------------------------------------------------------------------- -#[test] -fn min_revenue_threshold_default_is_zero() { - let (_env, client, issuer, token, _payout) = setup_with_offering(); - let threshold = client.get_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &0); - assert_eq!(threshold, 0); -} + #[test] + fn min_revenue_threshold_default_is_zero() { + let (_env, client, issuer, token, _payout) = setup_with_offering(); + let threshold = client.get_min_revenue_threshold(&issuer, &symbol_short!("def"), &token); + assert_eq!(threshold, 0); + } -#[test] -fn set_min_revenue_threshold_emits_event() { - let (env, client, issuer, token, _payout) = setup_with_offering(); - let before = env.events().all().len(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &5_000); - assert!(env.events().all().len() > before); -} + #[test] + fn set_min_revenue_threshold_emits_event() { + let (env, client, issuer, token, _payout) = setup_with_offering(); + let before = env.events().all().len(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &5_000); + assert!(env.events().all().len() > before); + } -#[test] -fn report_below_threshold_emits_event_and_skips_distribution() { - let (env, client, issuer, token, payout_asset) = setup_with_offering(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &10_000); - let events_before = env.events().all().len(); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); - let events_after = env.events().all().len(); - assert!(events_after > events_before, "should emit rev_below event"); - let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert!( - summary.is_none() || summary.as_ref().unwrap().report_count == 0, - "below-threshold report must not count toward audit" - ); -} + #[test] + fn report_below_threshold_emits_event_and_skips_distribution() { + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &10_000); + let events_before = env.events().all().len(); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); + let events_after = env.events().all().len(); + assert!(events_after > events_before, "should emit rev_below event"); + let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); + assert!( + summary.is_none() || summary.as_ref().unwrap().report_count == 0, + "below-threshold report must not count toward audit" + ); + } -#[test] -fn report_at_or_above_threshold_updates_state() { - let (_env, client, issuer, token, payout_asset) = setup_with_offering(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &1_000); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000, &1, &false); - let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert_eq!(summary.unwrap().report_count, 1); - assert_eq!(summary.unwrap().total_revenue, 1_000); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &2_000, &2, &false); - let summary2 = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert_eq!(summary2.report_count, 2); - assert_eq!(summary2.total_revenue, 3_000); -} + #[test] + fn report_at_or_above_threshold_updates_state() { + let (_env, client, issuer, token, payout_asset) = setup_with_offering(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &1_000); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000, + &1, + &false, + ); + let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); + assert_eq!(summary.report_count, 1); + assert_eq!(summary.total_revenue, 1_000); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &2_000, + &2, + &false, + ); + let summary2 = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); + assert_eq!(summary2.report_count, 2); + assert_eq!(summary2.total_revenue, 3_000); + } -#[test] -fn zero_threshold_disables_check() { - let (_env, client, issuer, token, payout_asset) = setup_with_offering(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &100); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &50, &1, &false); - let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); - assert_eq!(summary.unwrap().report_count, 1); -} + #[test] + fn zero_threshold_disables_check() { + let (_env, client, issuer, token, payout_asset) = setup_with_offering(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &100); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &0); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &50, + &1, + &false, + ); + let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); + assert_eq!(summary.report_count, 1); + } -#[test] -fn min_revenue_threshold_change_emits_event() { - let (env, client, issuer, token, _payout) = setup_with_offering(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &1_000); - let before = env.events().all().len(); - client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &2_000); - assert!(env.events().all().len() > before); - assert_eq!(client.get_min_revenue_threshold(&issuer, &symbol_short!("def"), &token), 2_000); -} + #[test] + fn min_revenue_threshold_change_emits_event() { + let (env, client, issuer, token, _payout) = setup_with_offering(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &1_000); + let before = env.events().all().len(); + client.set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &2_000); + assert!(env.events().all().len() > before); + assert_eq!(client.get_min_revenue_threshold(&issuer, &symbol_short!("def"), &token), 2_000); + } -// --------------------------------------------------------------------------- -// Deterministic ordering for query results (#38) -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Deterministic ordering for query results (#38) + // --------------------------------------------------------------------------- -#[test] -fn get_offerings_page_order_is_by_registration_index() { - let (env, client, issuer) = setup(); - let t0 = Address::generate(&env); - let t1 = Address::generate(&env); - let t2 = Address::generate(&env); - let t3 = Address::generate(&env); - let p0 = Address::generate(&env); - let p1 = Address::generate(&env); - let p2 = Address::generate(&env); - let p3 = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &t0, &100, &p0, &0); - client.register_offering(&issuer, &symbol_short!("def"), &t1, &200, &p1, &0); - client.register_offering(&issuer, &symbol_short!("def"), &t2, &300, &p2, &0); - client.register_offering(&issuer, &symbol_short!("def"), &t3, &400, &p3, &0); - let (page, _) = client.get_offerings_page(&issuer, &symbol_short!("def"), &0, &10); - assert_eq!(page.len(), 4); - assert_eq!(page.get(0).unwrap().token, t0); - assert_eq!(page.get(1).unwrap().token, t1); - assert_eq!(page.get(2).unwrap().token, t2); - assert_eq!(page.get(3).unwrap().token, t3); -} + #[test] + fn get_offerings_page_order_is_by_registration_index() { + let (env, client, issuer) = setup(); + let t0 = Address::generate(&env); + let t1 = Address::generate(&env); + let t2 = Address::generate(&env); + let t3 = Address::generate(&env); + let p0 = Address::generate(&env); + let p1 = Address::generate(&env); + let p2 = Address::generate(&env); + let p3 = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &t0, &100, &p0, &0); + client.register_offering(&issuer, &symbol_short!("def"), &t1, &200, &p1, &0); + client.register_offering(&issuer, &symbol_short!("def"), &t2, &300, &p2, &0); + client.register_offering(&issuer, &symbol_short!("def"), &t3, &400, &p3, &0); + let (page, _) = client.get_offerings_page(&issuer, &symbol_short!("def"), &0, &10); + assert_eq!(page.len(), 4); + assert_eq!(page.get(0).unwrap().token, t0); + assert_eq!(page.get(1).unwrap().token, t1); + assert_eq!(page.get(2).unwrap().token, t2); + assert_eq!(page.get(3).unwrap().token, t3); + } -#[test] -fn get_blacklist_order_is_by_insertion() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - let admin = Address::generate(&env); + #[test] + fn get_blacklist_order_is_by_insertion() { + let env = Env::default(); + env.mock_all_auths(); + let client = make_client(&env); + let admin = Address::generate(&env); let issuer = admin.clone(); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - let issuer = admin.clone(); - let a = Address::generate(&env); - let b = Address::generate(&env); - let c = Address::generate(&env); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &a); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &b); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &c); - let list = client.get_blacklist(&issuer, &symbol_short!("def"), &token); - assert_eq!(list.len(), 3); - assert_eq!(list.get(0).unwrap(), a); - assert_eq!(list.get(1).unwrap(), b); - assert_eq!(list.get(2).unwrap(), c); -} - -#[test] -fn get_blacklist_order_unchanged_after_remove() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - let admin = Address::generate(&env); + let token = Address::generate(&env); + let payout_asset = Address::generate(&env); let issuer = admin.clone(); + let a = Address::generate(&env); + let b = Address::generate(&env); + let c = Address::generate(&env); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &a); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &b); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &c); + let list = client.get_blacklist(&issuer, &symbol_short!("def"), &token); + assert_eq!(list.len(), 3); + assert_eq!(list.get(0).unwrap(), a); + assert_eq!(list.get(1).unwrap(), b); + assert_eq!(list.get(2).unwrap(), c); + } - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - let issuer = admin.clone(); - let a = Address::generate(&env); - let b = Address::generate(&env); - let c = Address::generate(&env); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &a); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &b); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &c); - client.blacklist_remove(&admin, &issuer, &symbol_short!("def"), &token, &b); - let list = client.get_blacklist(&issuer, &symbol_short!("def"), &token); - assert_eq!(list.len(), 2); - assert_eq!(list.get(0).unwrap(), a); - assert_eq!(list.get(1).unwrap(), c); -} - -#[test] -fn get_pending_periods_order_is_by_deposit_index() { - let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100, &10); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &200, &20); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &300, &30); - let holder = Address::generate(&env); - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &1_000); - let periods = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder); - assert_eq!(periods.len(), 3); - assert_eq!(periods.get(0).unwrap(), 10); - assert_eq!(periods.get(1).unwrap(), 20); - assert_eq!(periods.get(2).unwrap(), 30); -} - -// --------------------------------------------------------------------------- -// Contract version and migration (#23) -// --------------------------------------------------------------------------- + #[test] + fn get_blacklist_order_unchanged_after_remove() { + let env = Env::default(); + env.mock_all_auths(); + let client = make_client(&env); + let admin = Address::generate(&env); + let issuer = admin.clone(); -#[test] -fn get_version_returns_constant_version() { - let env = Env::default(); - let client = make_client(&env); - assert_eq!(client.get_version(), crate::CONTRACT_VERSION); -} + let token = Address::generate(&env); + let payout_asset = Address::generate(&env); + let issuer = admin.clone(); + let a = Address::generate(&env); + let b = Address::generate(&env); + let c = Address::generate(&env); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &a); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &b); + client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &c); + client.blacklist_remove(&admin, &issuer, &symbol_short!("def"), &token, &b); + let list = client.get_blacklist(&issuer, &symbol_short!("def"), &token); + assert_eq!(list.len(), 2); + assert_eq!(list.get(0).unwrap(), a); + assert_eq!(list.get(1).unwrap(), c); + } -#[test] -fn get_version_unchanged_after_operations() { - let (env, client, issuer) = setup(); - let v0 = client.get_version(); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); - assert_eq!(client.get_version(), v0); -} + #[test] + fn get_pending_periods_order_is_by_deposit_index() { + let (env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100, &10); + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &200, &20); + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &300, &30); + let holder = Address::generate(&env); + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &holder, &1_000); + let periods = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &holder); + assert_eq!(periods.len(), 3); + assert_eq!(periods.get(0).unwrap(), 10); + assert_eq!(periods.get(1).unwrap(), 20); + assert_eq!(periods.get(2).unwrap(), 30); + } -// --------------------------------------------------------------------------- -// Input parameter validation (#35) -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Contract version and migration (#23) + // --------------------------------------------------------------------------- -#[test] -fn deposit_revenue_rejects_zero_amount() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &0, &1); - assert!(r.is_err()); -} + #[test] + fn get_version_returns_constant_version() { + let env = Env::default(); + let client = make_client(&env); + assert_eq!(client.get_version(), crate::CONTRACT_VERSION); + } -#[test] -fn deposit_revenue_rejects_negative_amount() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &-1, &1); - assert!(r.is_err()); -} + #[test] + fn get_version_unchanged_after_operations() { + let (env, client, issuer) = setup(); + let v0 = client.get_version(); + let token = Address::generate(&env); + let payout_asset = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); + assert_eq!(client.get_version(), v0); + } -#[test] -fn deposit_revenue_rejects_zero_period_id() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100, &0); - assert!(r.is_err()); -} + // --------------------------------------------------------------------------- + // Input parameter validation (#35) + // --------------------------------------------------------------------------- -#[test] -fn deposit_revenue_accepts_minimum_valid_inputs() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - let r = client.try_deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &1, &1); - assert!(r.is_ok()); -} + #[test] + fn deposit_revenue_rejects_zero_amount() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &0, + &1, + ); + assert!(r.is_err()); + } -#[test] -fn report_revenue_rejects_negative_amount() { - let (_env, client, issuer, token, payout_asset) = setup_with_offering(); - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &-1, &1, &false); - assert!(r.is_err()); -} + #[test] + fn deposit_revenue_rejects_negative_amount() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &-1, + &1, + ); + assert!(r.is_err()); + } -#[test] -fn report_revenue_accepts_zero_amount() { - let (_env, client, issuer, token, payout_asset) = setup_with_offering(); - let r = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &0, &0, &false); - assert!(r.is_ok()); -} + #[test] + fn deposit_revenue_rejects_zero_period_id() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100, + &0, + ); + assert!(r.is_err()); + } -#[test] -fn set_min_revenue_threshold_rejects_negative() { - let (_env, client, issuer, token, _payout_asset) = setup_with_offering(); - let r = client.try_set_min_revenue_threshold(&issuer, &token, &-1); - assert!(r.is_err()); -} + #[test] + fn deposit_revenue_accepts_minimum_valid_inputs() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + let r = client.try_deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &1, + &1, + ); + assert!(r.is_ok()); + } -#[test] -fn set_min_revenue_threshold_accepts_zero() { - let (_env, client, issuer, token, _payout_asset) = setup_with_offering(); - let r = client.try_set_min_revenue_threshold(&issuer, &token, &0); - assert!(r.is_ok()); -} + #[test] + fn report_revenue_rejects_negative_amount() { + let (_env, client, issuer, token, payout_asset) = setup_with_offering(); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &-1, + &1, + &false, + ); + assert!(r.is_err()); + } -// --------------------------------------------------------------------------- -// Continuous invariants testing (#49) – randomized sequences, deterministic seed -// --------------------------------------------------------------------------- + #[test] + fn report_revenue_accepts_zero_amount() { + let (_env, client, issuer, token, payout_asset) = setup_with_offering(); + let r = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &0, + &0, + &false, + ); + assert!(r.is_ok()); + } -const INVARIANT_SEED: u64 = 0x1234_5678_9abc_def0; -/// Kept modest to stay within Soroban test budget (#49). -const INVARIANT_STEPS: usize = 24; + #[test] + fn set_min_revenue_threshold_rejects_negative() { + let (_env, client, issuer, token, _payout_asset) = setup_with_offering(); + let r = client.try_set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &-1); + assert!(r.is_err()); + } -/// Run one random step (deterministic given seed). -fn invariant_random_step( - env: &Env, - client: &RevoraRevenueShareClient, - issuers: &soroban_sdk::Vec
, - tokens: &soroban_sdk::Vec
, - payout_assets: &soroban_sdk::Vec
, - seed: &mut u64, -) { - let n_issuers = issuers.len() as usize; - let n_tokens = tokens.len() as usize; - let n_payout = payout_assets.len() as usize; - if n_issuers == 0 || n_tokens == 0 { - return; + #[test] + fn set_min_revenue_threshold_accepts_zero() { + let (_env, client, issuer, token, _payout_asset) = setup_with_offering(); + let r = client.try_set_min_revenue_threshold(&issuer, &symbol_short!("def"), &token, &0); + assert!(r.is_ok()); } - let op = next_u64(seed) % 6; - let issuer_idx = (next_u64(seed) as usize) % n_issuers; - let token_idx = (next_u64(seed) as usize) % n_tokens; - let issuer = issuers.get(issuer_idx as u32); - let token = tokens.get(token_idx as u32); - let payout_idx = token_idx.min(n_payout.saturating_sub(1)); - let payout = payout_assets.get(payout_idx as u32); - - match op { - 0 => { - let _ = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout, &0); - } - 1 => { - let amount = (next_u64(seed) % 1_000_000 + 1) as i128; - let period_id = next_period(seed) % 1_000_000 + 1; - let _ = - client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout, &amount, &period_id, &false); + + // --------------------------------------------------------------------------- + // Continuous invariants testing (#49) – randomized sequences, deterministic seed + // --------------------------------------------------------------------------- + + const INVARIANT_SEED: u64 = 0x1234_5678_9abc_def0; + /// Kept modest to stay within Soroban test budget (#49). + const INVARIANT_STEPS: usize = 24; + + /// Run one random step (deterministic given seed). + fn invariant_random_step( + env: &Env, + client: &RevoraRevenueShareClient, + issuers: &soroban_sdk::Vec
, + tokens: &soroban_sdk::Vec
, + payout_assets: &soroban_sdk::Vec
, + seed: &mut u64, + ) { + let n_issuers = issuers.len() as usize; + let n_tokens = tokens.len() as usize; + let n_payout = payout_assets.len() as usize; + if n_issuers == 0 || n_tokens == 0 { + return; } - 2 => { - let _ = client.try_set_concentration_limit(&issuer, &symbol_short!("def"), &token, &5000, &false); + let op = next_u64(seed) % 6; + let issuer_idx = (next_u64(seed) as usize) % n_issuers; + let token_idx = (next_u64(seed) as usize) % n_tokens; + let issuer = issuers.get(issuer_idx as u32).unwrap(); + let token = tokens.get(token_idx as u32).unwrap(); + let payout_idx = token_idx.min(n_payout.saturating_sub(1)); + let payout = payout_assets.get(payout_idx as u32).unwrap(); + + match op { + 0 => { + let _ = client.try_register_offering( + &issuer, + &symbol_short!("def"), + &token, + &1_000, + &payout, + &0, + ); + } + 1 => { + let amount = (next_u64(seed) % 1_000_000 + 1) as i128; + let period_id = next_period(seed) % 1_000_000 + 1; + let _ = client.try_report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout, + &amount, + &period_id, + &false, + ); + } + 2 => { + let _ = client.try_set_concentration_limit( + &issuer, + &symbol_short!("def"), + &token, + &5000, + &false, + ); + } + 3 => { + let conc_bps = (next_u64(seed) % 10_001) as u32; + let _ = client.try_report_concentration( + &issuer, + &symbol_short!("def"), + &token, + &conc_bps, + ); + } + 4 => { + let holder = Address::generate(env); + client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &holder); + } + 5 => { + client.blacklist_remove(&issuer, &issuer, &symbol_short!("def"), &token, &issuer); + } + _ => {} } - 3 => { - let conc_bps = (next_u64(seed) % 10_001) as u32; - let _ = client.try_report_concentration(&issuer, &token, &conc_bps); + } + + /// Check invariants that must hold after any step. + fn check_invariants(client: &RevoraRevenueShareClient, issuers: &soroban_sdk::Vec
) { + for i in 0..issuers.len() { + let issuer = issuers.get(i).unwrap(); + let count = client.get_offering_count(&issuer, &symbol_short!("def")); + let (page, cursor) = client.get_offerings_page(&issuer, &symbol_short!("def"), &0, &20); + assert_eq!(page.len(), count.min(20)); + assert!(count <= 200, "offering count bounded"); + if count > 0 { + assert!(cursor.is_some() || page.len() == count); + } } - 4 => { - let holder = Address::generate(env); - client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &holder); + let _v = client.get_version(); + assert!(_v >= 1); + } + + #[test] + fn continuous_invariants_after_random_operations() { + let env = Env::default(); + env.mock_all_auths(); + let client = make_client(&env); + let mut issuers_vec = Vec::new(&env); + let mut tokens_vec = Vec::new(&env); + let mut payout_vec = Vec::new(&env); + for _ in 0..4 { + issuers_vec.push_back(Address::generate(&env)); + let t = Address::generate(&env); + let p = Address::generate(&env); + tokens_vec.push_back(t); + payout_vec.push_back(p); } - 5 => { - client.blacklist_remove(&issuer, &issuer, &symbol_short!("def"), &token, &issuer); + let mut seed = INVARIANT_SEED; + + for _ in 0..INVARIANT_STEPS { + invariant_random_step(&env, &client, &issuers_vec, &tokens_vec, &payout_vec, &mut seed); + check_invariants(&client, &issuers_vec); } - _ => {} } -} -/// Check invariants that must hold after any step. -fn check_invariants(client: &RevoraRevenueShareClient, issuers: &soroban_sdk::Vec
) { - for i in 0..issuers.len() { - let issuer = issuers.get(i); - let count = client.get_offering_count(&issuer, &symbol_short!("def")); - let (page, cursor) = client.get_offerings_page(&issuer, &symbol_short!("def"), &0, &20); - assert_eq!(page.len(), count.min(20)); - assert!(count <= 200, "offering count bounded"); - if count > 0 { - assert!(cursor.is_some() || page.len() == count); + #[test] + fn continuous_invariants_deterministic_reproducible() { + let env1 = Env::default(); + env1.mock_all_auths(); + let client1 = make_client(&env1); + let mut iss1 = Vec::new(&env1); + let mut tok1 = Vec::new(&env1); + let mut pay1 = Vec::new(&env1); + iss1.push_back(Address::generate(&env1)); + tok1.push_back(Address::generate(&env1)); + pay1.push_back(Address::generate(&env1)); + let mut seed1 = INVARIANT_SEED; + for _ in 0..16 { + let _ = client1.try_register_offering( + &iss1.get(0).unwrap(), + &symbol_short!("def"), + &tok1.get(0).unwrap(), + &1000, + &pay1.get(0).unwrap(), + &0, + ); + invariant_random_step(&env1, &client1, &iss1, &tok1, &pay1, &mut seed1); } + let count1 = client1.get_offering_count(&iss1.get(0).unwrap(), &symbol_short!("def")); + + let env2 = Env::default(); + env2.mock_all_auths(); + let client2 = make_client(&env2); + let mut iss2 = Vec::new(&env2); + let mut tok2 = Vec::new(&env2); + let mut pay2 = Vec::new(&env2); + iss2.push_back(Address::generate(&env2)); + tok2.push_back(Address::generate(&env2)); + pay2.push_back(Address::generate(&env2)); + let mut seed2 = INVARIANT_SEED; + for _ in 0..16 { + let _ = client2.try_register_offering( + &iss2.get(0).unwrap(), + &symbol_short!("def"), + &tok2.get(0).unwrap(), + &1000, + &pay2.get(0).unwrap(), + &0, + ); + invariant_random_step(&env2, &client2, &iss2, &tok2, &pay2, &mut seed2); + } + let count2 = client2.get_offering_count(&iss2.get(0).unwrap(), &symbol_short!("def")); + assert_eq!(count1, count2, "same seed yields same operation sequence"); } - let _v = client.get_version(); - assert!(_v >= 1); -} -#[test] -fn continuous_invariants_after_random_operations() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - let mut issuers_vec = Vec::new(&env); - let mut tokens_vec = Vec::new(&env); - let mut payout_vec = Vec::new(&env); - for _ in 0..4 { - issuers_vec.push_back(Address::generate(&env)); - let t = Address::generate(&env); - let p = Address::generate(&env); - tokens_vec.push_back(t); - payout_vec.push_back(p); - } - let mut seed = INVARIANT_SEED; + // =========================================================================== + // Cross-offering aggregation query tests (#39) + // =========================================================================== - for _ in 0..INVARIANT_STEPS { - invariant_random_step( - &env, - &client, - &issuers_vec, - &tokens_vec, - &payout_vec, - &mut seed, - ); - check_invariants(&client, &issuers_vec); + #[test] + fn aggregation_empty_issuer_returns_zeroes() { + let (_env, client, issuer) = setup(); + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.total_reported_revenue, 0); + assert_eq!(metrics.total_deposited_revenue, 0); + assert_eq!(metrics.total_report_count, 0); + assert_eq!(metrics.offering_count, 0); } -} -#[test] -fn continuous_invariants_deterministic_reproducible() { - let env1 = Env::default(); - env1.mock_all_auths(); - let client1 = make_client(&env1); - let mut iss1 = Vec::new(&env1); - let mut tok1 = Vec::new(&env1); - let mut pay1 = Vec::new(&env1); - iss1.push_back(Address::generate(&env1)); - tok1.push_back(Address::generate(&env1)); - pay1.push_back(Address::generate(&env1)); - let mut seed1 = INVARIANT_SEED; - for _ in 0..16 { - let _ = client1.try_register_offering( - &iss1.get(0).unwrap(), - &tok1.get(0).unwrap(), - &1000, - &pay1.get(0).unwrap(), + #[test] + fn aggregation_single_offering_reported_revenue() { + let (env, client, issuer) = setup(); + let token = Address::generate(&env); + let payout_asset = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &100_000, + &1, + &false, ); - invariant_random_step(&env1, &client1, &iss1, &tok1, &pay1, &mut seed1); - } - let count1 = client1.get_offering_count(&iss1.get(0).unwrap()); - - let env2 = Env::default(); - env2.mock_all_auths(); - let client2 = make_client(&env2); - let mut iss2 = Vec::new(&env2); - let mut tok2 = Vec::new(&env2); - let mut pay2 = Vec::new(&env2); - iss2.push_back(Address::generate(&env2)); - tok2.push_back(Address::generate(&env2)); - pay2.push_back(Address::generate(&env2)); - let mut seed2 = INVARIANT_SEED; - for _ in 0..16 { - let _ = client2.try_register_offering( - &iss2.get(0).unwrap(), - &tok2.get(0).unwrap(), - &1000, - &pay2.get(0).unwrap(), + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &200_000, + &2, + &false, ); - invariant_random_step(&env2, &client2, &iss2, &tok2, &pay2, &mut seed2); - } - let count2 = client2.get_offering_count(&iss2.get(0).unwrap()); - assert_eq!(count1, count2, "same seed yields same operation sequence"); -} - -// =========================================================================== -// Cross-offering aggregation query tests (#39) -// =========================================================================== - -#[test] -fn aggregation_empty_issuer_returns_zeroes() { - let (_env, client, issuer) = setup(); - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.total_reported_revenue, 0); - assert_eq!(metrics.total_deposited_revenue, 0); - assert_eq!(metrics.total_report_count, 0); - assert_eq!(metrics.offering_count, 0); -} - -#[test] -fn aggregation_single_offering_reported_revenue() { - let (env, client, issuer) = setup(); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000, &1, &false); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &200_000, &2, &false); - - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.total_reported_revenue, 300_000); - assert_eq!(metrics.total_report_count, 2); - assert_eq!(metrics.offering_count, 1); - assert_eq!(metrics.total_deposited_revenue, 0); -} -#[test] -fn aggregation_multiple_offerings_same_issuer() { - let (env, client, issuer) = setup(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); - let payout_a = Address::generate(&env); - let payout_b = Address::generate(&env); + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.total_reported_revenue, 300_000); + assert_eq!(metrics.total_report_count, 2); + assert_eq!(metrics.offering_count, 1); + assert_eq!(metrics.total_deposited_revenue, 0); + } - client.register_offering(&issuer, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); - client.register_offering(&issuer, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); + #[test] + fn aggregation_multiple_offerings_same_issuer() { + let (env, client, issuer) = setup(); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); + let payout_a = Address::generate(&env); + let payout_b = Address::generate(&env); - client.report_revenue(&issuer, &symbol_short!("def"), &token_a, &payout_a, &100_000, &1, &false); - client.report_revenue(&issuer, &symbol_short!("def"), &token_b, &payout_b, &200_000, &1, &false); - client.report_revenue(&issuer, &symbol_short!("def"), &token_b, &payout_b, &300_000, &2, &false); + client.register_offering(&issuer, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.total_reported_revenue, 600_000); - assert_eq!(metrics.total_report_count, 3); - assert_eq!(metrics.offering_count, 2); -} + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token_a, + &payout_a, + &100_000, + &1, + &false, + ); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token_b, + &payout_b, + &200_000, + &1, + &false, + ); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token_b, + &payout_b, + &300_000, + &2, + &false, + ); -#[test] -fn aggregation_deposited_revenue_tracking() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.total_reported_revenue, 600_000); + assert_eq!(metrics.total_report_count, 3); + assert_eq!(metrics.offering_count, 2); + } - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &1); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &200_000, &2); + #[test] + fn aggregation_deposited_revenue_tracking() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &1, + ); + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &200_000, + &2, + ); - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.total_deposited_revenue, 300_000); - assert_eq!(metrics.offering_count, 1); -} + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.total_deposited_revenue, 300_000); + assert_eq!(metrics.offering_count, 1); + } -#[test] -fn aggregation_mixed_reported_and_deposited() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + #[test] + fn aggregation_mixed_reported_and_deposited() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); - // Report revenue - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &500_000, &1, &false); + // Report revenue + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &500_000, + &1, + &false, + ); - // Deposit revenue - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &100_000, &10); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &200_000, &20); + // Deposit revenue + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &100_000, + &10, + ); + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &200_000, + &20, + ); - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.total_reported_revenue, 500_000); - assert_eq!(metrics.total_deposited_revenue, 300_000); - assert_eq!(metrics.total_report_count, 1); - assert_eq!(metrics.offering_count, 1); -} + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.total_reported_revenue, 500_000); + assert_eq!(metrics.total_deposited_revenue, 300_000); + assert_eq!(metrics.total_report_count, 1); + assert_eq!(metrics.offering_count, 1); + } -#[test] -fn aggregation_per_issuer_isolation() { - let (env, client, issuer_a) = setup(); - let issuer_b = Address::generate(&env); + #[test] + fn aggregation_per_issuer_isolation() { + let (env, client, issuer_a) = setup(); + let issuer_b = Address::generate(&env); let issuer = issuer_b.clone(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); - let payout_a = Address::generate(&env); - let payout_b = Address::generate(&env); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); + let payout_a = Address::generate(&env); + let payout_b = Address::generate(&env); - client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a); - client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); - client.report_revenue(&issuer_a, &symbol_short!("def"), &token_a, &payout_a, &100_000, &1, &false); - client.report_revenue(&issuer_b, &symbol_short!("def"), &token_b, &payout_b, &500_000, &1, &false); + client.report_revenue( + &issuer_a, + &symbol_short!("def"), + &token_a, + &payout_a, + &100_000, + &1, + &false, + ); + client.report_revenue( + &issuer_b, + &symbol_short!("def"), + &token_b, + &payout_b, + &500_000, + &1, + &false, + ); - let metrics_a = client.get_issuer_aggregation(&issuer_a); - let metrics_b = client.get_issuer_aggregation(&issuer_b); + let metrics_a = client.get_issuer_aggregation(&issuer_a); + let metrics_b = client.get_issuer_aggregation(&issuer_b); - assert_eq!(metrics_a.total_reported_revenue, 100_000); - assert_eq!(metrics_a.offering_count, 1); - assert_eq!(metrics_b.total_reported_revenue, 500_000); - assert_eq!(metrics_b.offering_count, 1); -} + assert_eq!(metrics_a.total_reported_revenue, 100_000); + assert_eq!(metrics_a.offering_count, 1); + assert_eq!(metrics_b.total_reported_revenue, 500_000); + assert_eq!(metrics_b.offering_count, 1); + } -#[test] -fn platform_aggregation_empty() { - let (_env, client, _issuer) = setup(); - let metrics = client.get_platform_aggregation(); - assert_eq!(metrics.total_reported_revenue, 0); - assert_eq!(metrics.total_deposited_revenue, 0); - assert_eq!(metrics.total_report_count, 0); - assert_eq!(metrics.offering_count, 0); -} + #[test] + fn platform_aggregation_empty() { + let (_env, client, _issuer) = setup(); + let metrics = client.get_platform_aggregation(); + assert_eq!(metrics.total_reported_revenue, 0); + assert_eq!(metrics.total_deposited_revenue, 0); + assert_eq!(metrics.total_report_count, 0); + assert_eq!(metrics.offering_count, 0); + } -#[test] -fn platform_aggregation_single_issuer() { - let (env, client, issuer) = setup(); - let token = Address::generate(&env); - let payout = Address::generate(&env); + #[test] + fn platform_aggregation_single_issuer() { + let (env, client, issuer) = setup(); + let token = Address::generate(&env); + let payout = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout, &0); - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout, &100_000, &1, &false); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout, &0); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout, + &100_000, + &1, + &false, + ); - let metrics = client.get_platform_aggregation(); - assert_eq!(metrics.total_reported_revenue, 100_000); - assert_eq!(metrics.total_report_count, 1); - assert_eq!(metrics.offering_count, 1); -} + let metrics = client.get_platform_aggregation(); + assert_eq!(metrics.total_reported_revenue, 100_000); + assert_eq!(metrics.total_report_count, 1); + assert_eq!(metrics.offering_count, 1); + } -#[test] -fn platform_aggregation_multiple_issuers() { - let (env, client, issuer_a) = setup(); - let issuer_b = Address::generate(&env); + #[test] + fn platform_aggregation_multiple_issuers() { + let (env, client, issuer_a) = setup(); + let issuer_b = Address::generate(&env); let issuer = issuer_b.clone(); - let issuer_c = Address::generate(&env); + let issuer_c = Address::generate(&env); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); - let token_c = Address::generate(&env); - let payout_a = Address::generate(&env); - let payout_b = Address::generate(&env); - let payout_c = Address::generate(&env); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); + let token_c = Address::generate(&env); + let payout_a = Address::generate(&env); + let payout_b = Address::generate(&env); + let payout_c = Address::generate(&env); - client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a); - client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b); - client.register_offering(&issuer_c, &symbol_short!("def"), &token_c, &3_000, &payout_c); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); + client.register_offering(&issuer_c, &symbol_short!("def"), &token_c, &3_000, &payout_c, &0); - client.report_revenue(&issuer_a, &symbol_short!("def"), &token_a, &payout_a, &100_000, &1, &false); - client.report_revenue(&issuer_b, &symbol_short!("def"), &token_b, &payout_b, &200_000, &1, &false); - client.report_revenue(&issuer_c, &symbol_short!("def"), &token_c, &payout_c, &300_000, &1, &false); + client.report_revenue( + &issuer_a, + &symbol_short!("def"), + &token_a, + &payout_a, + &100_000, + &1, + &false, + ); + client.report_revenue( + &issuer_b, + &symbol_short!("def"), + &token_b, + &payout_b, + &200_000, + &1, + &false, + ); + client.report_revenue( + &issuer_c, + &symbol_short!("def"), + &token_c, + &payout_c, + &300_000, + &1, + &false, + ); - let metrics = client.get_platform_aggregation(); - assert_eq!(metrics.total_reported_revenue, 600_000); - assert_eq!(metrics.total_report_count, 3); - assert_eq!(metrics.offering_count, 3); -} + let metrics = client.get_platform_aggregation(); + assert_eq!(metrics.total_reported_revenue, 600_000); + assert_eq!(metrics.total_report_count, 3); + assert_eq!(metrics.offering_count, 3); + } -#[test] -fn get_all_issuers_returns_registered() { - let (env, client, issuer_a) = setup(); - let issuer_b = Address::generate(&env); + #[test] + fn get_all_issuers_returns_registered() { + let (env, client, issuer_a) = setup(); + let issuer_b = Address::generate(&env); let issuer = issuer_b.clone(); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); + let payout_a = Address::generate(&env); + let payout_b = Address::generate(&env); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); - let payout_a = Address::generate(&env); - let payout_b = Address::generate(&env); - - client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a); - client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b); - - let issuers = client.get_all_issuers(); - assert_eq!(issuers.len(), 2); - assert!(issuers.contains(&issuer_a)); - assert!(issuers.contains(&issuer_b)); -} - -#[test] -fn get_all_issuers_empty_when_none_registered() { - let (_env, client, _issuer) = setup(); - let issuers = client.get_all_issuers(); - assert_eq!(issuers.len(), 0); -} - -#[test] -fn issuer_registered_once_even_with_multiple_offerings() { - let (env, client, issuer) = setup(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); - let token_c = Address::generate(&env); - let payout_a = Address::generate(&env); - let payout_b = Address::generate(&env); - let payout_c = Address::generate(&env); - - client.register_offering(&issuer, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); - client.register_offering(&issuer, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); - client.register_offering(&issuer, &symbol_short!("def"), &token_c, &3_000, &payout_c, &0); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); - let issuers = client.get_all_issuers(); - assert_eq!(issuers.len(), 1); - assert_eq!(issuers.get(0).unwrap(), issuer); -} + let issuers = client.get_all_issuers(); + assert_eq!(issuers.len(), 2); + assert!(issuers.contains(&issuer_a)); + assert!(issuers.contains(&issuer_b)); + } -#[test] -fn get_total_deposited_revenue_per_offering() { - let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + #[test] + fn get_all_issuers_empty_when_none_registered() { + let (_env, client, _issuer) = setup(); + let issuers = client.get_all_issuers(); + assert_eq!(issuers.len(), 0); + } - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &50_000, &1); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &75_000, &2); - client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &125_000, &3); + #[test] + fn issuer_registered_once_even_with_multiple_offerings() { + let (env, client, issuer) = setup(); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); + let token_c = Address::generate(&env); + let payout_a = Address::generate(&env); + let payout_b = Address::generate(&env); + let payout_c = Address::generate(&env); + + client.register_offering(&issuer, &symbol_short!("def"), &token_a, &1_000, &payout_a, &0); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &2_000, &payout_b, &0); + client.register_offering(&issuer, &symbol_short!("def"), &token_c, &3_000, &payout_c, &0); + + let issuers = client.get_all_issuers(); + assert_eq!(issuers.len(), 1); + assert_eq!(issuers.get(0).unwrap(), issuer); + } - let total = client.get_total_deposited_revenue(&token); - assert_eq!(total, 250_000); -} + #[test] + fn get_total_deposited_revenue_per_offering() { + let (_env, client, issuer, token, payment_token, _contract_id) = claim_setup(); + + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &50_000, &1); + client.deposit_revenue(&issuer, &symbol_short!("def"), &token, &payment_token, &75_000, &2); + client.deposit_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payment_token, + &125_000, + &3, + ); -#[test] -fn get_total_deposited_revenue_zero_when_no_deposits() { - let (env, _client, _issuer) = setup(); - let client = make_client(&env); - let random_token = Address::generate(&env); - assert_eq!(client.get_total_deposited_revenue(&random_token), 0); -} + let total = client.get_total_deposited_revenue(&issuer, &symbol_short!("def"), &token); + assert_eq!(total, 250_000); + } -#[test] -fn aggregation_no_reports_only_offerings() { - let (env, client, issuer) = setup(); - register_n(&env, &client, &issuer, 5); + #[test] + fn get_total_deposited_revenue_zero_when_no_deposits() { + let (env, _client, issuer) = setup(); + let client = make_client(&env); + let random_token = Address::generate(&env); + assert_eq!( + client.get_total_deposited_revenue(&issuer, &symbol_short!("def"), &random_token), + 0 + ); + } - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.offering_count, 5); - assert_eq!(metrics.total_reported_revenue, 0); - assert_eq!(metrics.total_deposited_revenue, 0); - assert_eq!(metrics.total_report_count, 0); -} + #[test] + fn aggregation_no_reports_only_offerings() { + let (env, client, issuer) = setup(); + register_n(&env, &client, &issuer, 5); + + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.offering_count, 5); + assert_eq!(metrics.total_reported_revenue, 0); + assert_eq!(metrics.total_deposited_revenue, 0); + assert_eq!(metrics.total_report_count, 0); + } -#[test] -fn platform_aggregation_with_deposits_across_issuers() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, RevoraRevenueShare); - let client = RevoraRevenueShareClient::new(&env, &contract_id); + #[test] + fn platform_aggregation_with_deposits_across_issuers() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, RevoraRevenueShare); + let client = RevoraRevenueShareClient::new(&env, &contract_id); - let issuer_a = Address::generate(&env); + let issuer_a = Address::generate(&env); let issuer = issuer_a.clone(); - let issuer_b = Address::generate(&env); + let issuer_b = Address::generate(&env); let issuer = issuer_b.clone(); - let token_a = Address::generate(&env); - let token_b = Address::generate(&env); + let token_a = Address::generate(&env); + let token_b = Address::generate(&env); - let (pt_a, pt_a_admin) = create_payment_token(&env); - let (pt_b, pt_b_admin) = create_payment_token(&env); + let (pt_a, pt_a_admin) = create_payment_token(&env); + let (pt_b, pt_b_admin) = create_payment_token(&env); - client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &5_000, &pt_a); - client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &3_000, &pt_b); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &5_000, &pt_a, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &3_000, &pt_b, &0); - mint_tokens(&env, &pt_a, &pt_a_admin, &issuer_a, &5_000_000); - mint_tokens(&env, &pt_b, &pt_b_admin, &issuer_b, &5_000_000); + mint_tokens(&env, &pt_a, &pt_a_admin, &issuer_a, &5_000_000); + mint_tokens(&env, &pt_b, &pt_b_admin, &issuer_b, &5_000_000); - client.deposit_revenue(&issuer_a, &token_a, &pt_a, &100_000, &1); - client.deposit_revenue(&issuer_b, &token_b, &pt_b, &200_000, &1); + client.deposit_revenue(&issuer_a, &symbol_short!("def"), &token_a, &pt_a, &100_000, &1); + client.deposit_revenue(&issuer_b, &symbol_short!("def"), &token_b, &pt_b, &200_000, &1); - let metrics = client.get_platform_aggregation(); - assert_eq!(metrics.total_deposited_revenue, 300_000); - assert_eq!(metrics.offering_count, 2); -} + let metrics = client.get_platform_aggregation(); + assert_eq!(metrics.total_deposited_revenue, 300_000); + assert_eq!(metrics.offering_count, 2); + } -#[test] -fn aggregation_stress_many_offerings() { - let (env, client, issuer) = setup(); + #[test] + fn aggregation_stress_many_offerings() { + let (env, client, issuer) = setup(); + + // Register 20 offerings and report revenue on each + let mut tokens = soroban_sdk::Vec::new(&env); + let mut payouts = soroban_sdk::Vec::new(&env); + for _i in 0..20_u32 { + let token = Address::generate(&env); + let payout = Address::generate(&env); + tokens.push_back(token.clone()); + payouts.push_back(payout.clone()); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout, &0); + } - // Register 20 offerings and report revenue on each - let mut tokens = soroban_sdk::Vec::new(&env); - let mut payouts = soroban_sdk::Vec::new(&env); - for _i in 0..20_u32 { - let token = Address::generate(&env); - let payout = Address::generate(&env); - tokens.push_back(token.clone()); - payouts.push_back(payout.clone()); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout, &0); - } + for i in 0..20_u32 { + let token = tokens.get(i).unwrap(); + let payout = payouts.get(i).unwrap(); + client.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout, + &((i as i128 + 1) * 10_000), + &1, + &false, + ); + } - for i in 0..20_u32 { - let token = tokens.get(i); - let payout = payouts.get(i); - client.report_revenue( - &issuer, &symbol_short!("def"), &token, - &payout, - &((i as i128 + 1) * 10_000), - &1, - &false, - ); + let metrics = client.get_issuer_aggregation(&issuer); + assert_eq!(metrics.offering_count, 20); + // Sum of 10_000 + 20_000 + ... + 200_000 = 10_000 * (1 + 2 + ... + 20) = 10_000 * 210 = 2_100_000 + assert_eq!(metrics.total_reported_revenue, 2_100_000); + assert_eq!(metrics.total_report_count, 20); } - - let metrics = client.get_issuer_aggregation(&issuer); - assert_eq!(metrics.offering_count, 20); - // Sum of 10_000 + 20_000 + ... + 200_000 = 10_000 * (1 + 2 + ... + 20) = 10_000 * 210 = 2_100_000 - assert_eq!(metrics.total_reported_revenue, 2_100_000); - assert_eq!(metrics.total_report_count, 20); -} } // mod regression // =========================================================================== diff --git a/src/test_auth.rs b/src/test_auth.rs index d4d6fac4..15630b64 100644 --- a/src/test_auth.rs +++ b/src/test_auth.rs @@ -19,7 +19,7 @@ fn setup_offering(env: &Env, client: &RevoraRevenueShareClient) -> (Address, Add env.mock_all_auths(); let issuer = Address::generate(env); let token = Address::generate(env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token, &0); (issuer, token) } @@ -130,7 +130,7 @@ fn register_offering_missing_auth_no_mutation() { let issuer = Address::generate(&env); let token = Address::generate(&env); assert!(client - .try_register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token) + .try_register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token, &0) .is_err()); assert_eq!(client.get_offering_count(&issuer, &symbol_short!("def")), 0); } @@ -144,7 +144,7 @@ fn report_revenue_wrong_issuer_no_mutation() { assert!(client .try_report_revenue(&attacker, &symbol_short!("def"), &token, &token, &100, &1u64, &false) .is_err()); - assert!(client.get_audit_summary(&issuer, &token).is_none()); + assert!(client.get_audit_summary(&issuer, &symbol_short!("def"), &token).is_none()); } #[test] @@ -157,7 +157,7 @@ fn deposit_revenue_wrong_issuer_no_mutation() { assert!(client .try_deposit_revenue(&attacker, &symbol_short!("def"), &token, &payment_token, &100, &1u64) .is_err()); - assert_eq!(client.get_period_count(&token), 0); + assert_eq!(client.get_period_count(&issuer, &symbol_short!("def"), &token), 0); } #[test] @@ -182,7 +182,7 @@ fn set_concentration_limit_wrong_issuer_no_mutation() { assert!(client .try_set_concentration_limit(&attacker, &symbol_short!("def"), &token, &1_000u32, &true) .is_err()); - assert!(client.get_concentration_limit(&issuer, &token).is_none()); + assert!(client.get_concentration_limit(&issuer, &symbol_short!("def"), &token).is_none()); } #[test] @@ -192,10 +192,10 @@ fn set_rounding_mode_wrong_issuer_no_mutation() { let (issuer, token) = setup_offering(&env, &client); let attacker = Address::generate(&env); assert!(client - .try_set_rounding_mode(&attacker, &symbol_short!("def"), &token, RoundingMode::RoundHalfUp) + .try_set_rounding_mode(&attacker, &symbol_short!("def"), &token, &RoundingMode::RoundHalfUp) .is_err()); assert_eq!( - client.get_rounding_mode(&issuer, &token), + client.get_rounding_mode(&issuer, &symbol_short!("def"), &token), RoundingMode::Truncation ); } @@ -209,7 +209,7 @@ fn set_min_revenue_threshold_wrong_issuer_no_mutation() { assert!(client .try_set_min_revenue_threshold(&attacker, &symbol_short!("def"), &token, &123i128) .is_err()); - assert_eq!(client.get_min_revenue_threshold(&issuer, &token), 0); + assert_eq!(client.get_min_revenue_threshold(&issuer, &symbol_short!("def"), &token), 0); } #[test] @@ -218,9 +218,7 @@ fn set_claim_delay_wrong_issuer_no_mutation() { let client = make_client(&env); let (issuer, token) = setup_offering(&env, &client); let attacker = Address::generate(&env); - assert!(client - .try_set_claim_delay(&attacker, &symbol_short!("def"), &token, &100u64) - .is_err()); + assert!(client.try_set_claim_delay(&attacker, &symbol_short!("def"), &token, &100u64).is_err()); assert_eq!(client.get_claim_delay(&issuer, &symbol_short!("def"), &token), 0); } @@ -232,9 +230,9 @@ fn set_offering_metadata_wrong_issuer_no_mutation() { let attacker = Address::generate(&env); let meta: SdkString = SdkString::from_str(&env, "m"); assert!(client - .try_set_offering_metadata(&attacker, &symbol_short!("def"), &token, meta) + .try_set_offering_metadata(&attacker, &symbol_short!("def"), &token, &meta) .is_err()); - assert!(client.get_offering_metadata(&issuer, &token).is_none()); + assert!(client.get_offering_metadata(&issuer, &symbol_short!("def"), &token).is_none()); } #[test] @@ -260,7 +258,7 @@ fn blacklist_remove_wrong_caller_no_mutation() { let issuer = Address::generate(&env); let token = Address::generate(&env); let investor = Address::generate(&env); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token); + client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &token, &0); client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &investor); let attacker = Address::generate(&env); assert!(client @@ -279,12 +277,12 @@ fn cross_offering_confusion_wrong_issuer_no_mutation() { let token_a = Address::generate(&env); let token_b = Address::generate(&env); let holder = Address::generate(&env); - client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &token_a); - client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &1_000, &token_b); + client.register_offering(&issuer_a, &symbol_short!("def"), &token_a, &1_000, &token_a, &0); + client.register_offering(&issuer_b, &symbol_short!("def"), &token_b, &1_000, &token_b, &0); assert!(client - .try_set_holder_share(&issuer_b, &token_a, &holder, &1_000u32) + .try_set_holder_share(&issuer_b, &symbol_short!("def"), &token_a, &holder, &1_000u32) .is_err()); - assert_eq!(client.get_holder_share(&token_a, &holder), 0); + assert_eq!(client.get_holder_share(&issuer_a, &symbol_short!("def"), &token_a, &holder), 0); } #[test] @@ -293,5 +291,6 @@ fn claim_missing_auth_no_mutation() { let client = make_client(&env); let holder = Address::generate(&env); let token = Address::generate(&env); - assert!(client.try_claim(&holder, &issuer, &symbol_short!("def"), &token).is_err()); + let issuer = Address::generate(&env); + assert!(client.try_claim(&holder, &issuer, &symbol_short!("def"), &token, &0).is_err()); } diff --git a/src/test_cross_contract.rs b/src/test_cross_contract.rs index 75355a8c..3cfee489 100644 --- a/src/test_cross_contract.rs +++ b/src/test_cross_contract.rs @@ -101,10 +101,7 @@ impl MockTokenRegistry { /// Check if a token is approved. pub fn is_approved(env: Env, token: Address) -> bool { let key = (symbol_short!("approved"), token); - env.storage() - .persistent() - .get::<_, bool>(&key) - .unwrap_or(false) + env.storage().persistent().get::<_, bool>(&key).unwrap_or(false) } /// Remove a token from the approved list. @@ -144,9 +141,7 @@ impl MockPriceOracle { pub fn set_price(env: Env, admin: Address, asset: Address, price: i128, timestamp: u64) { admin.require_auth(); let key = (symbol_short!("price"), asset); - env.storage() - .persistent() - .set(&key, &PriceData { price, timestamp }); + env.storage().persistent().set(&key, &PriceData { price, timestamp }); } /// Get the current price for an asset. Returns None if not set. @@ -198,7 +193,7 @@ fn cross_contract_registry_check_before_offering() { // Step 3: Now register offering in Revora (after registry approval) // In production, register_offering would call registry.is_approved() internally assert!(registry.is_approved(&token)); - revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); // Verify offering was created let offering = revora.get_offering(&issuer, &symbol_short!("def"), &token); @@ -290,16 +285,24 @@ fn cross_contract_oracle_revenue_valuation() { let payout_asset = Address::generate(&env); // Register offering - revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); // Report revenue - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000_000, &1, &false); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &1_000_000, + &1, + &false, + ); // Set payout asset price: $1.00 (1_000_000 scaled) oracle.set_price(&admin, &payout_asset, &1_000_000, &1000); // Get audit summary and compute USD value - let summary = revora.get_audit_summary(&issuer, &token); + let summary = revora.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); let usd_value = oracle.calculate_value(&payout_asset, &summary.total_revenue); assert_eq!(usd_value, 1_000_000); // $1M revenue at $1/token } @@ -353,16 +356,32 @@ fn cross_contract_full_workflow_registry_then_offering() { assert!(registry.is_approved(&token)); // Step 3: Register offering in Revora - revora.register_offering(&issuer, &symbol_short!("def"), &token, &3_000, &payout_asset); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &3_000, &payout_asset, &0); // Step 4: Report revenue - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &500_000, &1, &false); - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &300_000, &2, &false); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &500_000, + &1, + &false, + ); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &300_000, + &2, + &false, + ); // Step 5: Set price and compute valuation oracle.set_price(&admin, &payout_asset, &2_000_000, &1000); // $2.00/token - let summary = revora.get_audit_summary(&issuer, &token); + let summary = revora.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); assert_eq!(summary.total_revenue, 800_000); assert_eq!(summary.report_count, 2); @@ -411,7 +430,7 @@ fn cross_contract_error_in_registry_does_not_affect_revora() { // Register token and offering registry.register_token(&admin, &token); - revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); // Cause an error in registry (duplicate registration) let err = registry.try_register_token(&admin, &token); @@ -439,10 +458,18 @@ fn cross_contract_revora_operations_independent_of_oracle_state() { let payout_asset = Address::generate(&env); // Revora works fine without any oracle prices set - revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset); - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000, &1, &false); - - let summary = revora.get_audit_summary(&issuer, &token); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &100_000, + &1, + &false, + ); + + let summary = revora.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); assert_eq!(summary.total_revenue, 100_000); // Oracle has no data - querying returns None @@ -475,13 +502,13 @@ fn cross_contract_concurrent_state_changes() { let payout_3 = Address::generate(&env); registry.register_token(&admin, &token_1); - revora.register_offering(&issuer, &symbol_short!("def"), &token_1, &1_000, &payout_1); + revora.register_offering(&issuer, &symbol_short!("def"), &token_1, &1_000, &payout_1, &0); registry.register_token(&admin, &token_2); - revora.register_offering(&issuer, &symbol_short!("def"), &token_2, &2_000, &payout_2); + revora.register_offering(&issuer, &symbol_short!("def"), &token_2, &2_000, &payout_2, &0); registry.register_token(&admin, &token_3); - revora.register_offering(&issuer, &symbol_short!("def"), &token_3, &3_000, &payout_3); + revora.register_offering(&issuer, &symbol_short!("def"), &token_3, &3_000, &payout_3, &0); // Verify both contracts have consistent state assert_eq!(registry.get_token_count(), 3); @@ -515,19 +542,35 @@ fn cross_contract_oracle_price_updates_across_revenue_reports() { let token = Address::generate(&env); let payout_asset = Address::generate(&env); - revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset); + revora.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); // Report revenue at $1.00 - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000, &1, &false); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &100_000, + &1, + &false, + ); oracle.set_price(&admin, &payout_asset, &1_000_000, &100); let val_1 = oracle.calculate_value(&payout_asset, &100_000); assert_eq!(val_1, 100_000); // 100k * $1 // Price doubles to $2.00 oracle.set_price(&admin, &payout_asset, &2_000_000, &200); - revora.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000, &2, &false); - - let summary = revora.get_audit_summary(&issuer, &token); + revora.report_revenue( + &issuer, + &symbol_short!("def"), + &token, + &payout_asset, + &100_000, + &2, + &false, + ); + + let summary = revora.get_audit_summary(&issuer, &symbol_short!("def"), &token).unwrap(); let val_total = oracle.calculate_value(&payout_asset, &summary.total_revenue); assert_eq!(val_total, 400_000); // 200k * $2 = $400k at current price } @@ -574,10 +617,19 @@ fn cross_contract_stress_revora_with_registry_and_oracle() { let payout = Address::generate(&env); registry.register_token(&admin, &token); - revora.register_offering(&issuer, &symbol_short!("def"), &token, &((i + 1) * 1_000), &payout); + revora.register_offering( + &issuer, + &symbol_short!("def"), + &token, + &((i + 1) * 1_000), + &payout, + &0, + ); oracle.set_price(&admin, &payout, &((i as i128 + 1) * 1_000_000), &1000); revora.report_revenue( - &issuer, &symbol_short!("def"), &token, + &issuer, + &symbol_short!("def"), + &token, &payout, &((i as i128 + 1) * 100_000), &1, diff --git a/src/test_namespaces.rs b/src/test_namespaces.rs index e016ee2d..be723d50 100644 --- a/src/test_namespaces.rs +++ b/src/test_namespaces.rs @@ -1,13 +1,7 @@ #![cfg(test)] -use soroban_sdk::{ - symbol_short, - testutils::Address as _, - Address, Env, -}; -use crate::{ - RevoraRevenueShare, RevoraRevenueShareClient, -}; +use crate::{RevoraRevenueShare, RevoraRevenueShareClient}; +use soroban_sdk::{symbol_short, testutils::Address as _, Address, Env}; fn make_client(env: &Env) -> RevoraRevenueShareClient<'_> { let id = env.register_contract(None, RevoraRevenueShare); @@ -42,10 +36,10 @@ fn test_namespace_isolation() { assert_eq!(client.get_holder_share(&issuer_b, &ns_2, &token, &holder), 1500); // We need to manage the token (mint some to the issuer) - // Actually, in mock_all_auths, the transfer will succeed if we don't check balances? + // Actually, in mock_all_auths, the transfer will succeed if we don't check balances? // No, soroban-sdk mock_all_auths doesn't mock balances. // But we are using the `token` Address directly. We should probably use a proper token client. - + // For simplicity in this isolation test, let's just check metadata/config which are simple set/get client.set_claim_delay(&issuer_a, &ns_1, &token, &3600); client.set_claim_delay(&issuer_b, &ns_2, &token, &7200); @@ -72,6 +66,6 @@ fn test_same_issuer_different_namespaces() { client.set_snapshot_config(&issuer, &ns_1, &token, &true); client.set_snapshot_config(&issuer, &ns_2, &token, &false); - assert_eq!(client.get_snapshot_config(&issuer, &ns_1, &token), true); - assert_eq!(client.get_snapshot_config(&issuer, &ns_2, &token), false); + assert!(client.get_snapshot_config(&issuer, &ns_1, &token)); + assert!(!client.get_snapshot_config(&issuer, &ns_2, &token)); } diff --git a/src/vesting.rs b/src/vesting.rs index af40202f..3e73fb1f 100644 --- a/src/vesting.rs +++ b/src/vesting.rs @@ -51,7 +51,8 @@ pub struct RevoraVesting; #[contractimpl] impl RevoraVesting { /// Initialize the vesting contract with an admin. - pub fn initialize(env: Env, admin: Address) -> Result<(), VestingError> { + /// Renamed to `initialize_vesting` to avoid symbol conflicts with other contracts. + pub fn initialize_vesting(env: Env, admin: Address) -> Result<(), VestingError> { if env.storage().persistent().has(&VestingDataKey::Admin) { return Err(VestingError::Unauthorized); } @@ -136,11 +137,8 @@ impl RevoraVesting { return Err(VestingError::Unauthorized); } let key = VestingDataKey::Schedule(admin.clone(), schedule_index); - let mut schedule: VestingSchedule = env - .storage() - .persistent() - .get(&key) - .ok_or(VestingError::ScheduleNotFound)?; + let mut schedule: VestingSchedule = + env.storage().persistent().get(&key).ok_or(VestingError::ScheduleNotFound)?; if schedule.beneficiary != beneficiary { return Err(VestingError::ScheduleNotFound); } @@ -175,14 +173,17 @@ impl RevoraVesting { } /// Claim vested tokens. Callable by beneficiary. - pub fn claim(env: Env, beneficiary: Address, admin: Address, schedule_index: u32) -> Result { + /// Renamed to `claim_vesting` to avoid symbol conflicts with other contracts. + pub fn claim_vesting( + env: Env, + beneficiary: Address, + admin: Address, + schedule_index: u32, + ) -> Result { beneficiary.require_auth(); let key = VestingDataKey::Schedule(admin.clone(), schedule_index); - let mut schedule: VestingSchedule = env - .storage() - .persistent() - .get(&key) - .ok_or(VestingError::ScheduleNotFound)?; + let mut schedule: VestingSchedule = + env.storage().persistent().get(&key).ok_or(VestingError::ScheduleNotFound)?; if schedule.beneficiary != beneficiary { return Err(VestingError::ScheduleNotFound); } @@ -218,14 +219,12 @@ impl RevoraVesting { schedule_index: u32, ) -> Result { let key = VestingDataKey::Schedule(admin, schedule_index); - env.storage() - .persistent() - .get(&key) - .ok_or(VestingError::ScheduleNotFound) + env.storage().persistent().get(&key).ok_or(VestingError::ScheduleNotFound) } /// Claimable amount for a schedule (vested minus already claimed). - pub fn get_claimable( + /// Renamed to `get_claimable_vesting` to avoid symbol conflicts with other contracts. + pub fn get_claimable_vesting( env: Env, admin: Address, schedule_index: u32, @@ -237,9 +236,6 @@ impl RevoraVesting { /// Number of schedules created by an admin. pub fn get_schedule_count(env: Env, admin: Address) -> u32 { - env.storage() - .persistent() - .get(&VestingDataKey::ScheduleCount(admin)) - .unwrap_or(0) + env.storage().persistent().get(&VestingDataKey::ScheduleCount(admin)).unwrap_or(0) } } diff --git a/src/vesting_test.rs b/src/vesting_test.rs index 19f4e7a8..d2d533e2 100644 --- a/src/vesting_test.rs +++ b/src/vesting_test.rs @@ -1,4 +1,3 @@ - use soroban_sdk::{ testutils::{Address as _, Ledger as _}, Address, Env, @@ -20,7 +19,7 @@ fn initialize_sets_admin() { let env = Env::default(); env.mock_all_auths(); let (client, admin, _b, _t) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); } #[test] @@ -28,14 +27,15 @@ fn create_schedule_success() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); let total = 1_000_000_i128; let start = 1000_u64; let cliff = 500_u64; let duration = 2000_u64; - let idx = client.create_schedule(&admin, &beneficiary, &token_id, &total, &start, &cliff, &duration); + let idx = + client.create_schedule(&admin, &beneficiary, &token_id, &total, &start, &cliff, &duration); assert_eq!(idx, 0); let schedule = client.get_schedule(&admin, &0); @@ -53,7 +53,7 @@ fn get_claimable_before_cliff_is_zero() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); let total = 1_000_000_i128; let start = 1000_u64; @@ -62,7 +62,7 @@ fn get_claimable_before_cliff_is_zero() { client.create_schedule(&admin, &beneficiary, &token_id, &total, &start, &cliff, &duration); env.ledger().with_mut(|l| l.timestamp = start + 100); - let claimable = client.get_claimable(&admin, &0); + let claimable = client.get_claimable_vesting(&admin, &0); assert_eq!(claimable, 0); } @@ -71,10 +71,10 @@ fn cancel_schedule() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); client.create_schedule(&admin, &beneficiary, &token_id, &1_000_000, &1000, &100, &2000); - let _ = client.cancel_schedule(&admin, &beneficiary, &0); + client.cancel_schedule(&admin, &beneficiary, &0); let schedule = client.get_schedule(&admin, &0); assert!(schedule.cancelled); } @@ -84,7 +84,7 @@ fn multiple_schedules_same_beneficiary() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); client.create_schedule(&admin, &beneficiary, &token_id, &100, &1000, &0, &1000); client.create_schedule(&admin, &beneficiary, &token_id, &200, &2000, &0, &1000); @@ -96,7 +96,7 @@ fn zero_duration_rejected() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); let r = client.try_create_schedule(&admin, &beneficiary, &token_id, &1000, &1000, &0, &0); assert!(r.is_err()); } @@ -106,7 +106,7 @@ fn cliff_longer_than_duration_rejected() { let env = Env::default(); env.mock_all_auths(); let (client, admin, beneficiary, token_id) = setup(&env); - let _ = client.initialize(&admin); + client.initialize_vesting(&admin); let r = client.try_create_schedule(&admin, &beneficiary, &token_id, &1000, &1000, &2000, &1000); assert!(r.is_err()); }