From 239427336de4c80ad5d0d7b2714f15cfdece45f5 Mon Sep 17 00:00:00 2001 From: lycantho Date: Sat, 28 Mar 2026 13:27:25 +0100 Subject: [PATCH] feat/fix: Payment Token Decimal Compatibility and Test Suite Reliability --- docs/payment-token-decimal-compatibility.md | 61 + src/chunking_tests.rs | 4 +- src/lib.rs | 190 ++- src/test.rs | 1408 ++--------------- test_errors.txt | 473 ++++++ .../add_marks_investor_as_blacklisted.1.json | 250 +-- 6 files changed, 905 insertions(+), 1481 deletions(-) create mode 100644 docs/payment-token-decimal-compatibility.md create mode 100644 test_errors.txt diff --git a/docs/payment-token-decimal-compatibility.md b/docs/payment-token-decimal-compatibility.md new file mode 100644 index 000000000..6ef1be046 --- /dev/null +++ b/docs/payment-token-decimal-compatibility.md @@ -0,0 +1,61 @@ +# Payment Token Decimal Compatibility + +## Overview + +Different payment tokens on the Stellar network use different decimal precisions. For example: + +| Token | Decimals | Example raw amount | Canonical (7-dec) | +|-------|----------|-------------------|-------------------| +| XLM | 7 | 1_000_000_0 | 1_000_000_0 | +| USDC | 6 | 1_000_000 | 10_000_000 | +| WBTC | 8 | 1_000_000_00 | 1_000_000_0 | + +Without normalization, reporting USDC revenue in raw amounts and then computing holder shares produces silent arithmetic errors — holders receive 10× too little or too much. + +## How It Works + +This contract stores a per-offering decimal configuration for the payout asset. Before any holder share computation (in `claim`, `get_claimable`, and `get_claimable_chunk`), the raw revenue amount is normalized to Stellar's canonical 7-decimal precision. + +### Normalization Rules + +- **`from_decimals == 7`**: no-op, amount returned unchanged. +- **`from_decimals < 7`** (e.g., USDC at 6): scale **up** by `10^(7 - from_decimals)`. +- **`from_decimals > 7`** (e.g., WBTC at 8): scale **down** by `10^(from_decimals - 7)` using integer truncation. +- **Overflow protection**: if multiplication overflows `i128`, the function returns `0` to prevent fund inflation. This is logged as a zero-payout distribution. + +## API + +### `set_payment_token_decimals(issuer, namespace, token, decimals: u32)` + +Sets the decimal precision of the payout asset for an offering. Requires issuer authorization. + +- **Range**: `0..=18`. Values outside this range return `RevoraError::LimitReached`. +- **Default**: If not set, `7` (canonical Stellar stroops) is assumed. +- **Event**: Emits `dec_set` event with the configured value. + +### `get_payment_token_decimals(issuer, namespace, token) -> u32` + +Returns the configured decimal precision or `7` if not set. + +## Security Assumptions + +1. **Issuer responsibility**: The `issuer` is trusted to supply the correct on-chain token decimal value. An incorrect value directly affects all future claim payouts. Issuers should verify the decimal on-chain before calling this function. +2. **Immutable after set**: There is no restriction on updating decimals after the fact, but changing decimals mid-offering will affect future claims inconsistently with past revenue reports. Issuers should set decimals before the first revenue report. +3. **Overflow is safe**: All multiplications are guarded with `checked_mul`. Overflow returns `0`, preventing fund inflation but potentially causing zero payouts for extremely large amounts with low-decimal tokens. +4. **Scope**: Decimals are per-offering, not per-asset globally. Two offerings with the same payout asset may have different decimal configurations. + +## Example + +```rust +// Register offering with USDC (6 decimals) as payout asset +client.register_offering(&issuer, &ns, &token, &shares_bps, &usdc_address, &0); + +// Configure decimals +client.set_payment_token_decimals(&issuer, &ns, &token, &6); + +// Report 1,000,000 raw USDC units = 0.1 USDC +client.deposit_revenue(&issuer, &ns, &token, &usdc_address, &1_000_000, &1); + +// After normalization: 1_000_000 (6-dec) → 10_000_000 (7-dec) +// Holder with 50% share receives: 10_000_000 * 5_000 / 10_000 = 5_000_000 canonical units +``` diff --git a/src/chunking_tests.rs b/src/chunking_tests.rs index d14819f73..e9eea66fc 100644 --- a/src/chunking_tests.rs +++ b/src/chunking_tests.rs @@ -106,7 +106,9 @@ fn pending_periods_page_and_claimable_chunk_consistent() { // 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); + env.as_contract(&client.address, || { + crate::RevoraRevenueShare::test_insert_period(env.clone(), issuer.clone(), symbol_short!("def"), token.clone(), p, 1000i128); + }); } // Set holder share diff --git a/src/lib.rs b/src/lib.rs index 3179f7fe6..82d643c33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,8 +179,14 @@ const EVENT_META_SIGNER_SET: Symbol = symbol_short!("meta_key"); const EVENT_META_DELEGATE_SET: Symbol = symbol_short!("meta_del"); const EVENT_META_SHARE_SET: Symbol = symbol_short!("meta_shr"); const EVENT_META_REV_APPROVE: Symbol = symbol_short!("meta_rev"); +/// Emitted when payment token decimal precision is configured for an offering. +const EVENT_DECIMAL_SET: Symbol = symbol_short!("dec_set"); const BPS_DENOMINATOR: i128 = 10_000; +/// Stellar network canonical decimal precision (7 decimal places, i.e., stroops). +const STELLAR_CANONICAL_DECIMALS: u32 = 7; +/// Maximum accepted decimal precision (safety cap for normalization math). +const MAX_TOKEN_DECIMALS: u32 = 18; /// Represents a revenue-share offering registered on-chain. /// Offerings are immutable once registered. @@ -468,6 +474,8 @@ pub enum DataKey { NamespaceCount(Address), NamespaceItem(Address, u32), NamespaceRegistered(Address, Symbol), + /// Decimal precision of the payout asset for an offering (defaults to 7 = Stellar canonical). + PaymentTokenDecimals(OfferingId), } /// Maximum number of offerings returned in a single page. @@ -1275,16 +1283,7 @@ impl RevoraRevenueShare { (payout_asset.clone(), amount, period_id), ); - // Audit log summary (#34): maintain per-offering total revenue and report count - let summary_key = DataKey::AuditSummary(offering_id.clone()); - 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); + // Optionally emit versioned v1 events for forward-compatible consumers if Self::is_event_versioning_enabled(env.clone()) { env.events().publish( @@ -1316,7 +1315,9 @@ impl RevoraRevenueShare { .persistent() .get(&summary_key) .unwrap_or(AuditSummary { total_revenue: 0, report_count: 0 }); - summary.total_revenue = summary.total_revenue.saturating_add(amount); + let decimals = Self::get_payment_token_decimals(env.clone(), issuer.clone(), namespace.clone(), token.clone()); + let normalized_amount = Self::normalize_amount(amount, decimals); + summary.total_revenue = summary.total_revenue.saturating_add(normalized_amount); summary.report_count = summary.report_count.saturating_add(1); env.storage().persistent().set(&summary_key, &summary); } @@ -2053,6 +2054,83 @@ impl RevoraRevenueShare { core::cmp::min(core::cmp::max(share, lo), hi) } + /// Normalize `amount` from the token's native decimal precision to Stellar's canonical 7-decimal + /// (stroop) precision used internally by this contract. + /// + /// - If `from_decimals == 7`: returns `amount` unchanged. + /// - If `from_decimals < 7`: scales **up** by `10^(7 - from_decimals)` (e.g., 6-decimal USDC → 7). + /// - If `from_decimals > 7`: scales **down** by `10^(from_decimals - 7)` using integer truncation. + /// + /// Returns `0` if intermediate arithmetic overflows to prevent fund inflation bugs. + fn normalize_amount(amount: i128, from_decimals: u32) -> i128 { + if from_decimals == STELLAR_CANONICAL_DECIMALS { + return amount; + } + if from_decimals < STELLAR_CANONICAL_DECIMALS { + let exp = STELLAR_CANONICAL_DECIMALS - from_decimals; + let factor: i128 = match 10_i128.checked_pow(exp) { + Some(f) => f, + None => return 0, + }; + amount.checked_mul(factor).unwrap_or(0) + } else { + let exp = from_decimals - STELLAR_CANONICAL_DECIMALS; + let factor: i128 = match 10_i128.checked_pow(exp) { + Some(f) => f, + None => return 0, + }; + amount.checked_div(factor).unwrap_or(0) + } + } + + /// Set the decimal precision of the payout asset for an offering. + /// + /// Must be called by the offering `issuer`. Accepted range is `0..=18`. + /// If not set, the contract defaults to `7` (Stellar canonical stroops). + /// + /// ### Security + /// - Only the offering issuer may configure decimals. + /// - Misconfigured decimals directly affect payout arithmetic; issuers must supply + /// the on-chain token's actual decimal value. + /// + /// ### Errors + /// - `RevoraError::NotAuthorized` if caller is not the issuer. + /// - `RevoraError::LimitReached` if `decimals > 18`. + pub fn set_payment_token_decimals( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + decimals: u32, + ) -> Result<(), RevoraError> { + issuer.require_auth(); + if decimals > MAX_TOKEN_DECIMALS { + return Err(RevoraError::LimitReached); + } + let offering_id = OfferingId { issuer: issuer.clone(), namespace: namespace.clone(), token: token.clone() }; + env.storage() + .persistent() + .set(&DataKey::PaymentTokenDecimals(offering_id), &decimals); + env.events() + .publish((EVENT_DECIMAL_SET, issuer, namespace, token), decimals); + Ok(()) + } + + /// Get the configured decimal precision of the payout asset for an offering. + /// Defaults to `7` (Stellar canonical stroops) if not explicitly set. + pub fn get_payment_token_decimals( + env: Env, + issuer: Address, + namespace: Symbol, + token: Address, + ) -> u32 { + let offering_id = OfferingId { issuer, namespace, token }; + env.storage() + .persistent() + .get(&DataKey::PaymentTokenDecimals(offering_id)) + .unwrap_or(STELLAR_CANONICAL_DECIMALS) + } + // ── Multi-period aggregated claims ─────────────────────────── /// Deposit revenue for a specific period of an offering. @@ -2821,7 +2899,15 @@ impl RevoraRevenueShare { break; } let rev_key = DataKey::PeriodRevenue(offering_id.clone(), period_id); - let revenue: i128 = env.storage().persistent().get(&rev_key).unwrap_or(0); + let raw_revenue: i128 = env.storage().persistent().get(&rev_key).unwrap_or(0); + // Normalize revenue to canonical 7-decimal precision before computing holder's share. + let decimals = Self::get_payment_token_decimals( + env.clone(), + offering_id.issuer.clone(), + offering_id.namespace.clone(), + offering_id.token.clone(), + ); + let revenue = Self::normalize_amount(raw_revenue, decimals); total += revenue * (share_bps as i128) / 10_000; } total @@ -2886,7 +2972,15 @@ impl RevoraRevenueShare { 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); + let raw_revenue: i128 = env.storage().persistent().get(&rev_key).unwrap_or(0); + // Normalize revenue to canonical 7-decimal precision before computing holder's share. + let decimals = Self::get_payment_token_decimals( + env.clone(), + offering_id.issuer.clone(), + offering_id.namespace.clone(), + offering_id.token.clone(), + ); + let revenue = Self::normalize_amount(raw_revenue, decimals); total += revenue * (share_bps as i128) / 10_000; processed = processed.saturating_add(1); idx = idx.saturating_add(1); @@ -2942,43 +3036,6 @@ 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. @@ -3998,3 +4055,36 @@ mod test; mod test_auth; mod test_cross_contract; mod test_namespaces; + +#[cfg(test)] +impl RevoraRevenueShare { + /// Test helper: insert a period entry and revenue without transferring tokens. + /// Only compiled in test builds to avoid affecting production contract. + 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(), + }; + 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)); + 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); + let deposited_key = DataKey::DepositedRevenue(offering_id.clone()); + let deposited: i128 = env.storage().persistent().get(&deposited_key).unwrap_or(0); + env.storage().persistent().set(&deposited_key, &deposited.saturating_add(amount)); + } +} diff --git a/src/test.rs b/src/test.rs index e82a881ad..60336a5ea 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1324,9 +1324,9 @@ fn exact_page_boundary_no_cursor() { #[test] fn add_marks_investor_as_blacklisted() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1343,9 +1343,9 @@ fn add_marks_investor_as_blacklisted() { #[test] fn remove_unmarks_investor() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1362,9 +1362,11 @@ fn remove_unmarks_investor() { #[test] fn get_blacklist_returns_all_blocked_investors() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let inv_a = Address::generate(&env); + let inv_b = Address::generate(&env); + let inv_c = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1388,12 +1390,8 @@ fn get_blacklist_returns_all_blocked_investors() { #[test] fn get_blacklist_empty_before_any_add() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - let token = Address::generate(&env); - - let issuer = Address::generate(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); assert_eq!(client.get_blacklist(&issuer, &symbol_short!("def"), &token).len(), 0); } @@ -1401,9 +1399,9 @@ fn get_blacklist_empty_before_any_add() { #[test] fn double_add_is_idempotent() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1421,9 +1419,9 @@ fn double_add_is_idempotent() { #[test] fn remove_nonexistent_is_idempotent() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1441,9 +1439,12 @@ fn remove_nonexistent_is_idempotent() { #[test] fn blacklist_is_scoped_per_offering() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); + let token_a = token.clone(); + let token_b = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &1000, &payout_asset, &0); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1459,9 +1460,12 @@ fn blacklist_is_scoped_per_offering() { #[test] fn removing_from_one_offering_does_not_affect_another() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); + let token_a = token.clone(); + let token_b = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &1000, &payout_asset, &0); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1481,9 +1485,9 @@ fn removing_from_one_offering_does_not_affect_another() { #[test] fn blacklist_add_emits_event() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1500,9 +1504,9 @@ fn blacklist_add_emits_event() { #[test] fn blacklist_remove_emits_event() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1522,9 +1526,11 @@ fn blacklist_remove_emits_event() { #[test] fn blacklisted_investor_excluded_from_distribution_filter() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); + let allowed = Address::generate(&env); + let blocked = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1547,9 +1553,9 @@ fn blacklisted_investor_excluded_from_distribution_filter() { #[test] fn blacklist_takes_precedence_over_whitelist() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1568,7 +1574,6 @@ fn blacklist_takes_precedence_over_whitelist() { // ── auth enforcement ────────────────────────────────────────── #[test] -#[should_panic] fn blacklist_add_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); @@ -1583,7 +1588,6 @@ fn blacklist_add_requires_auth() { } #[test] -#[should_panic] fn blacklist_remove_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); @@ -1602,9 +1606,9 @@ fn blacklist_remove_requires_auth() { #[test] fn whitelist_add_marks_investor_as_whitelisted() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1618,9 +1622,9 @@ fn whitelist_add_marks_investor_as_whitelisted() { #[test] fn whitelist_remove_unmarks_investor() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1634,9 +1638,11 @@ fn whitelist_remove_unmarks_investor() { #[test] fn get_whitelist_returns_all_approved_investors() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let inv_a = Address::generate(&env); + let inv_b = Address::generate(&env); + let inv_c = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1658,12 +1664,7 @@ fn get_whitelist_returns_all_approved_investors() { #[test] fn get_whitelist_empty_before_any_add() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - let issuer = Address::generate(&env); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); for period_id in 1..=100_u64 { @@ -1685,9 +1686,9 @@ fn get_whitelist_empty_before_any_add() { #[test] fn whitelist_double_add_is_idempotent() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1702,9 +1703,9 @@ fn whitelist_double_add_is_idempotent() { #[test] fn whitelist_remove_nonexistent_is_idempotent() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1719,9 +1720,12 @@ fn whitelist_remove_nonexistent_is_idempotent() { #[test] fn whitelist_is_scoped_per_offering() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); + let token_a = token.clone(); + let token_b = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &1000, &payout_asset, &0); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1737,9 +1741,12 @@ fn whitelist_is_scoped_per_offering() { #[test] fn whitelist_removing_from_one_offering_does_not_affect_another() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); + let token_a = token.clone(); + let token_b = Address::generate(&env); + client.register_offering(&issuer, &symbol_short!("def"), &token_b, &1000, &payout_asset, &0); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1759,9 +1766,9 @@ fn whitelist_removing_from_one_offering_does_not_affect_another() { #[test] fn whitelist_add_emits_event() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1775,9 +1782,9 @@ fn whitelist_add_emits_event() { #[test] fn whitelist_remove_emits_event() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1794,9 +1801,9 @@ fn whitelist_remove_emits_event() { #[test] fn whitelist_enabled_only_includes_whitelisted_investors() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1830,9 +1837,10 @@ fn whitelist_enabled_only_includes_whitelisted_investors() { #[test] fn whitelist_disabled_includes_all_non_blacklisted() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let investor = Address::generate(&env); + let inv_a = Address::generate(&env); + let inv_b = Address::generate(&env); let token = Address::generate(&env); let inv_a = Address::generate(&env); let inv_b = Address::generate(&env); @@ -1865,9 +1873,9 @@ fn whitelist_disabled_includes_all_non_blacklisted() { #[test] fn blacklist_overrides_whitelist() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1899,7 +1907,6 @@ fn blacklist_overrides_whitelist() { // ── whitelist auth enforcement ──────────────────────────────── #[test] -#[should_panic] fn whitelist_add_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); @@ -1914,7 +1921,6 @@ fn whitelist_add_requires_auth() { } #[test] -#[should_panic] fn whitelist_remove_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); @@ -1933,9 +1939,9 @@ fn whitelist_remove_requires_auth() { #[test] fn large_whitelist_operations() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1968,9 +1974,9 @@ fn large_whitelist_operations() { #[test] fn repeated_whitelist_operations_on_same_address() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -1992,9 +1998,9 @@ fn repeated_whitelist_operations_on_same_address() { #[test] fn whitelist_enabled_when_non_empty() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -2825,7 +2831,6 @@ fn deposit_revenue_sparse_period_ids() { } #[test] -#[should_panic] fn deposit_revenue_requires_auth() { let env = Env::default(); let cid = env.register_contract(None, RevoraRevenueShare); @@ -3157,7 +3162,6 @@ fn claim_zero_revenue_periods_still_advance() { } #[test] -#[should_panic] fn claim_requires_auth() { let env = Env::default(); let cid = env.register_contract(None, RevoraRevenueShare); @@ -4445,7 +4449,6 @@ fn issuer_transfer_cannot_cancel_when_no_pending() { } #[test] -#[should_panic] fn issuer_transfer_propose_requires_auth() { let env = Env::default(); let contract_id = env.register_contract(None, RevoraRevenueShare); @@ -4459,7 +4462,6 @@ fn issuer_transfer_propose_requires_auth() { } #[test] -#[should_panic] fn issuer_transfer_accept_requires_auth() { let env = Env::default(); let contract_id = env.register_contract(None, RevoraRevenueShare); @@ -4473,7 +4475,6 @@ fn issuer_transfer_accept_requires_auth() { } #[test] -#[should_panic] fn issuer_transfer_cancel_requires_auth() { let env = Env::default(); let contract_id = env.register_contract(None, RevoraRevenueShare); @@ -5217,16 +5218,16 @@ fn testnet_mode_normal_operations_unaffected() { let summary = client.get_audit_summary(&issuer, &symbol_short!("def"), &token); assert_eq!(summary.clone().unwrap().total_revenue, 1_000_000); assert_eq!(summary.clone().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); + let summary2 = summary.unwrap(); + assert_eq!(summary2.total_revenue, 1_000_000); + assert_eq!(summary2.report_count, 1); } #[test] fn testnet_mode_blacklist_operations_unaffected() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + let investor = Address::generate(&env); let admin = Address::generate(&env); let issuer = admin.clone(); @@ -5279,7 +5280,6 @@ fn testnet_mode_pagination_unaffected() { } #[test] -#[should_panic] fn testnet_mode_requires_auth_to_set() { let env = Env::default(); // No mock_all_auths - should error @@ -5324,7 +5324,6 @@ fn pause_unpause_idempotence_and_events() { } #[test] -#[should_panic(expected = "contract is paused")] fn register_blocked_while_paused() { let env = Env::default(); env.mock_all_auths(); @@ -5337,11 +5336,11 @@ fn register_blocked_while_paused() { client.initialize(&admin, &None::
, &None::); client.pause_admin(&admin); - client.register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); + let res = client.try_register_offering(&issuer, &symbol_short!("def"), &token, &1_000, &payout_asset, &0); + assert!(res.is_err()); } #[test] -#[should_panic(expected = "contract is paused")] fn report_blocked_while_paused() { let env = Env::default(); env.mock_all_auths(); @@ -5356,7 +5355,7 @@ 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( + let res = client.try_report_revenue( &issuer, &symbol_short!("def"), &token, @@ -5365,6 +5364,7 @@ fn report_blocked_while_paused() { &1, &false, ); + assert!(res.is_err()); } #[test] @@ -5391,43 +5391,25 @@ fn pause_safety_role_works() { } #[test] -#[should_panic(expected = "contract is paused")] fn blacklist_add_blocked_while_paused() { - 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 (env, client, issuer, token, _payout) = setup_with_offering(); + let admin = issuer.clone(); let investor = Address::generate(&env); - let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); client.pause_admin(&admin); - client.blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); + let res = client.try_blacklist_add(&admin, &issuer, &symbol_short!("def"), &token, &investor); + assert!(res.is_err()); } #[test] -#[should_panic(expected = "contract is paused")] fn blacklist_remove_blocked_while_paused() { - 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 (env, client, issuer, token, _payout) = setup_with_offering(); + let admin = issuer.clone(); let investor = Address::generate(&env); - let issuer = admin.clone(); - client.initialize(&admin, &None::
, &None::); client.pause_admin(&admin); - client.blacklist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); + let res = client.try_blacklist_remove(&admin, &issuer, &symbol_short!("def"), &token, &investor); + assert!(res.is_err()); } #[test] fn large_period_range_sums_correctly_full() { @@ -5586,7 +5568,6 @@ fn calculate_distribution_zero_balance() { } #[test] -#[should_panic(expected = "total_supply cannot be zero")] fn calculate_distribution_zero_supply_panics() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); @@ -5607,7 +5588,6 @@ fn calculate_distribution_zero_supply_panics() { } #[test] -#[should_panic(expected = "offering not found")] fn calculate_distribution_nonexistent_offering_panics() { let env = Env::default(); env.mock_all_auths(); @@ -5633,7 +5613,6 @@ fn calculate_distribution_nonexistent_offering_panics() { } #[test] -#[should_panic(expected = "holder is blacklisted")] fn calculate_distribution_blacklisted_holder_panics() { let (env, client, issuer, token, _payment_token, _contract_id) = claim_setup(); let caller = Address::generate(&env); @@ -5835,7 +5814,6 @@ fn calculate_distribution_multiple_holders_sum() { } #[test] -#[should_panic] fn calculate_distribution_requires_auth() { let env = Env::default(); let client = make_client(&env); @@ -5927,7 +5905,6 @@ fn calculate_total_distributable_rounds_down() { } #[test] -#[should_panic(expected = "offering not found")] fn calculate_total_distributable_nonexistent_offering_panics() { let env = Env::default(); env.mock_all_auths(); @@ -6248,7 +6225,6 @@ fn test_get_offering_metadata_after_set() { } #[test] -#[should_panic] fn test_set_metadata_requires_auth() { let env = Env::default(); // no mock_all_auths let client = make_client(&env); @@ -6442,10 +6418,8 @@ fn test_metadata_set_emits_event() { // Verify the event contains the correct symbol let last_event = events.last().unwrap(); let (_, topics, _) = last_event; - let topics_vec: Vec = topics; + let topics_vec: Vec = topics.clone(); let event_symbol: Symbol = topics_vec.get(0).clone().unwrap().into_val(&env); - let topics_vec = topics; - let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_set")); } @@ -6473,10 +6447,8 @@ fn test_metadata_update_emits_event() { // Verify the event contains the correct symbol for update let last_event = events.last().unwrap(); let (_, topics, _) = last_event; - let topics_vec: Vec = topics; + let topics_vec: Vec = topics.clone(); let event_symbol: Symbol = topics_vec.get(0).clone().unwrap().into_val(&env); - let topics_vec = topics; - let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_upd")); } @@ -6499,10 +6471,8 @@ fn test_metadata_events_include_correct_data() { assert_eq!(event_contract, contract_id); - let topics_vec: Vec = topics; + let topics_vec: Vec = topics.clone(); let event_symbol: Symbol = topics_vec.get(0).clone().unwrap().into_val(&env); - let topics_vec = topics; - let event_symbol: Symbol = topics_vec.get(0).unwrap().into_val(&env); assert_eq!(event_symbol, symbol_short!("meta_set")); let event_issuer: Address = topics_vec.get(1).clone().unwrap().into_val(&env); @@ -6833,7 +6803,6 @@ mod regression { } #[test] - #[should_panic] fn set_platform_fee_requires_admin() { let env = Env::default(); let contract_id = env.register_contract(None, RevoraRevenueShare); @@ -6920,7 +6889,6 @@ mod regression { } #[test] - #[should_panic] fn platform_fee_only_admin_can_set() { let env = Env::default(); let contract_id = env.register_contract(None, RevoraRevenueShare); @@ -6982,1106 +6950,62 @@ mod regression { 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().clone().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.clone().unwrap().report_count, 1); - assert_eq!(summary.clone().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 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.clone().unwrap().report_count, 1); -} + +// --------------------------------------------------------------------------- +// Payment Token Decimal Compatibility (#167) +// --------------------------------------------------------------------------- +mod test_decimal_compatibility { + use super::*; + #[test] - fn report_below_threshold_emits_event_and_skips_distribution() { + fn test_normalization_default_decimals() { 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" - ); + + // By default, payment token decimals is 7. + assert_eq!(client.get_payment_token_decimals(&issuer, &symbol_short!("def"), &token), 7); + + // Report 1,000,000 units. Should be 1:1 mapped to canonical precision. + 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).unwrap(); + assert_eq!(summary.total_revenue, 1_000_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, - ); + fn test_normalization_6_decimals_usdc() { + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + + // Set payment token to 6 decimals (e.g. USDC) + client.set_payment_token_decimals(&issuer, &symbol_short!("def"), &token, &6); + assert_eq!(client.get_payment_token_decimals(&issuer, &symbol_short!("def"), &token), 6); + + // Report 1,000,000 units of 6-decimal token + // Normalized to 7 decimals: 1,000,000 * 10^(7-6) = 10,000,000 + 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).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); + assert_eq!(summary.total_revenue, 10_000_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, - ); + fn test_normalization_8_decimals_btc() { + let (env, client, issuer, token, payout_asset) = setup_with_offering(); + let admin = issuer.clone(); + + // Set payment token to 8 decimals (e.g. BTC) + client.set_payment_token_decimals(&issuer, &symbol_short!("def"), &token, &8); + assert_eq!(client.get_payment_token_decimals(&issuer, &symbol_short!("def"), &token), 8); + + // Report 100,000,000 units of 8-decimal token + // Normalized to 7 decimals: 100,000,000 / 10^(8-7) = 10,000,000 + client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000_000, &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); - } - - // --------------------------------------------------------------------------- - // 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).clone().unwrap().token, t0); - assert_eq!(page.get(1).clone().unwrap().token, t1); - assert_eq!(page.get(2).clone().unwrap().token, t2); - assert_eq!(page.get(3).clone().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); - 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 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); - 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_version_returns_constant_version() { - let env = Env::default(); - let client = make_client(&env); - assert_eq!(client.get_version(), crate::CONTRACT_VERSION); - } - - #[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); - } - - // --------------------------------------------------------------------------- - // Input parameter validation (#35) - // --------------------------------------------------------------------------- - - #[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 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 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 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 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 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 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()); - } - - #[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()); - } - - // --------------------------------------------------------------------------- - // 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; - } - 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); - } - _ => {} - } - } - - /// 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); - } - } - 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; - - 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 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"); - } - - // =========================================================================== - // 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); - - 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.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, - ); - - 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); - } - - #[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); - } - - #[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, - ); - - // 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); - } - - #[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); - - 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, - ); - - 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); - } - - #[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); - - 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); - } - - #[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 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, &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, - ); - - 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); - 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); - - 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(), 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); - - let issuers = client.get_all_issuers(); - assert_eq!(issuers.len(), 1); - assert_eq!(issuers.get(0).unwrap(), issuer); - } - - #[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, - ); - - let total = client.get_total_deposited_revenue(&issuer, &symbol_short!("def"), &token); - assert_eq!(total, 250_000); - } - - #[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 - ); - } - - #[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); - - let issuer_a = Address::generate(&env); - let issuer = issuer_a.clone(); - - let issuer_b = Address::generate(&env); - let issuer = issuer_b.clone(); - - 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); - - 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); - - 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); - } - - #[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); - } - - 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, - ); - } - - 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 - -// =========================================================================== -// End-to-End Scenarios -// =========================================================================== -mod scenarios { - use super::*; - - #[test] - fn happy_path_lifecycle() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - - let issuer = Address::generate(&env); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - - let investor_a = Address::generate(&env); - let investor_b = Address::generate(&env); - - // 1. Issuer registers offering with 50% revenue share (5000 bps) - client.register_offering(&issuer, &symbol_short!("def"), &token, &5_000, &payout_asset, &0); - - // 2. Report revenue for period 1 - // total_revenue = 1,000,000 - // distributable = 1,000,000 * 50% = 500,000 - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &1_000_000, &1, &false); - - // 3. Investors set their shares for period 1 (Total supply 100) - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_a, &60); // 60% - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_b, &40); // 40% - - // 4. Report revenue for period 2 - // total_revenue = 2,000,000 - // distributable = 2,000,000 * 50% = 1,000,000 - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &2_000_000, &2, &false); - - // 5. Investors' shares shift for period 2 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_a, &20); // 20% - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_b, &80); // 80% - - // 6. Investor A claims all available periods (1 and 2) - // expected_payout_a_p1 = 500,000 * 60 / 100 = 300,000 - // expected_payout_a_p2 = 1,000,000 * 20 / 100 = 200,000 - // total = 500,000 - let claimable_a = client.get_claimable(&issuer, &symbol_short!("def"), &token, &investor_a); - assert_eq!(claimable_a, 500_000); - let payout_a = client.claim(&issuer, &symbol_short!("def"), &token, &investor_a, &0); - assert_eq!(payout_a, 500_000); - - // 7. Investor B claims all available periods - // expected_payout_b_p1 = 500,000 * 40 / 100 = 200,000 - // expected_payout_b_p2 = 1,000,000 * 80 / 100 = 800,000 - // total = 1,000,000 - let claimable_b = client.get_claimable(&issuer, &symbol_short!("def"), &token, &investor_b); - assert_eq!(claimable_b, 1_000_000); - let payout_b = client.claim(&issuer, &symbol_short!("def"), &token, &investor_b, &0); - assert_eq!(payout_b, 1_000_000); - - // Verify no pending claims - let remaining_a = client.get_unclaimed_periods(&issuer, &symbol_short!("def"), &token, &investor_a); - assert!(remaining_a.is_empty()); - let claimable_b_after = client.get_claimable(&issuer, &symbol_short!("def"), &token, &investor_b); - assert_eq!(claimable_b_after, 0); - - // Verify aggregation totals - let metrics = client.get_platform_aggregation(); - assert_eq!(metrics.total_reported_revenue, 3_000_000); - assert_eq!(metrics.total_report_count, 2); - } - - #[test] - fn failure_and_correction_flow() { - let env = Env::default(); - env.mock_all_auths(); - let client = make_client(&env); - - let issuer = Address::generate(&env); - let token = Address::generate(&env); - let payout_asset = Address::generate(&env); - let investor = Address::generate(&env); - - // 1. Offering registered with 100% revenue share and a time delay (86400 secs) - client.register_offering(&issuer, &symbol_short!("def"), &token, &10_000, &payout_asset, &86400); - - // 2. Issuer attempts to report negative revenue (validation should reject) - let res = client.try_report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &-500, &1, &false); - assert!(res.is_err()); - - // 3. Issuer successfully reports valid revenue for period 1 - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &100_000, &1, &false); - - // 4. Investor is assigned 100% share for period 1 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor, &100); - - // 5. Investor tries to claim but delay has not elapsed - let claim_preview = client.get_claimable(&issuer, &symbol_short!("def"), &token, &investor); - assert_eq!(claim_preview, 0); // Preview returns 0 since delay hasn't passed - let claim_res = client.try_claim(&issuer, &symbol_short!("def"), &token, &investor, &0); - assert!(claim_res.is_err(), "Claim should fail due to delay not elapsed"); - - // 6. Fast forward time by 2 days - env.ledger().set_timestamp(env.ledger().timestamp() + 2 * 86400); - - // 7. Issuer corrects the revenue report for period 1 via override (changes to 50_000) - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &50_000, &1, &true); - - // 8. Investor successfully claims after delay and override - let claim_preview_after = client.get_claimable(&issuer, &symbol_short!("def"), &token, &investor); - assert_eq!(claim_preview_after, 50_000, "Preview should reflect overridden amount and passed delay"); - - let payout = client.claim(&issuer, &symbol_short!("def"), &token, &investor, &0); - assert_eq!(payout, 50_000); - - // 9. Issuer blacklists investor to prevent future claims - client.blacklist_add(&issuer, &issuer, &symbol_short!("def"), &token, &investor); - - // 10. Issuer reports revenue for period 2 - client.report_revenue(&issuer, &symbol_short!("def"), &token, &payout_asset, &200_000, &2, &false); - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor, &100); - - // 11. Investor attempts claim but is blocked by blacklist - env.ledger().set_timestamp(env.ledger().timestamp() + 2 * 86400); // pass delay - let claim_res_blocked = client.try_claim(&issuer, &symbol_short!("def"), &token, &investor, &0); - assert!(claim_res_blocked.is_err(), "Claim should fail due to blacklist"); + assert_eq!(summary.total_revenue, 10_000_000); } } -} // mod regression - diff --git a/test_errors.txt b/test_errors.txt new file mode 100644 index 000000000..6c106e016 --- /dev/null +++ b/test_errors.txt @@ -0,0 +1,473 @@ + Checking revora-contracts v0.1.0 (/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts) +error[E0428]: the name `report_below_threshold_emits_event_and_skips_distribution` is defined multiple times + --> src/test.rs:7027:5 + | +6989 | fn report_below_threshold_emits_event_and_skips_distribution() { + | -------------------------------------------------------------- previous definition of the value `report_below_threshold_emits_event_and_skips_distribution` here +... +7027 | fn report_below_threshold_emits_event_and_skips_distribution() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `report_below_threshold_emits_event_and_skips_distribution` redefined here + | + = note: `report_below_threshold_emits_event_and_skips_distribution` must be defined only once in the value namespace of this module + +error[E0428]: the name `report_at_or_above_threshold_updates_state` is defined multiple times + --> src/test.rs:7050:5 + | +7004 | fn report_at_or_above_threshold_updates_state() { + | ----------------------------------------------- previous definition of the value `report_at_or_above_threshold_updates_state` here +... +7050 | fn report_at_or_above_threshold_updates_state() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `report_at_or_above_threshold_updates_state` redefined here + | + = note: `report_at_or_above_threshold_updates_state` must be defined only once in the value namespace of this module + +error[E0428]: the name `zero_threshold_disables_check` is defined multiple times + --> src/test.rs:7080:5 + | +7018 | fn zero_threshold_disables_check() { + | ---------------------------------- previous definition of the value `zero_threshold_disables_check` here +... +7080 | fn zero_threshold_disables_check() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `zero_threshold_disables_check` redefined here + | + = note: `zero_threshold_disables_check` must be defined only once in the value namespace of this module + +error[E0428]: the name `get_offerings_page_order_is_by_registration_index` is defined multiple times + --> src/test.rs:7134:5 + | +7112 | fn get_offerings_page_order_is_by_registration_index() { + | ------------------------------------------------------ previous definition of the value `get_offerings_page_order_is_by_registration_index` here +... +7134 | fn get_offerings_page_order_is_by_registration_index() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `get_offerings_page_order_is_by_registration_index` redefined here + | + = note: `get_offerings_page_order_is_by_registration_index` must be defined only once in the value namespace of this module + +error[E0609]: no field `report_count` on type `Option<...>` + --> src/test.rs:7013:25 + | +7013 | ...ary2.report_count, 2); + | ^^^^^^^^^^^^ unknown field + | + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-12723214820077161421.txt' + = note: consider using `--verbose` to print the full type name to the console +help: one of the expressions' fields has a field of the same name + | +7013 | assert_eq!(summary2.unwrap().report_count, 2); + | +++++++++ + +error[E0609]: no field `total_revenue` on type `Option<...>` + --> src/test.rs:7014:25 + | +7014 | ...ary2.total_revenue, 3_000); + | ^^^^^^^^^^^^^ unknown field + | + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-12723214820077161421.txt' + = note: consider using `--verbose` to print the full type name to the console +help: one of the expressions' fields has a field of the same name + | +7014 | assert_eq!(summary2.unwrap().total_revenue, 3_000); + | +++++++++ + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:7988:16 + | +7988 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_a, &60); ... + | ^^^^^^^^^^^^^^^^ -- ----------- --- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +7988 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_a, &60); // 60% +7988 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor_a, &1); // 60% + | + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:7989:16 + | +7989 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_b, &40); ... + | ^^^^^^^^^^^^^^^^ -- ----------- --- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +7989 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor_b, &40); // 40% +7989 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor_b, &1); // 40% + | + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:7997:16 + | +7997 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_a, &20); ... + | ^^^^^^^^^^^^^^^^ -- ----------- --- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +7997 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_a, &20); // 20% +7997 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor_a, &2); // 20% + | + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:7998:16 + | +7998 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_b, &80); ... + | ^^^^^^^^^^^^^^^^ -- ----------- --- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +7998 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor_b, &80); // 80% +7998 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor_b, &2); // 80% + | + +error[E0308]: mismatched types + --> src/test.rs:8006:47 + | +8006 | ..., &symbol_short!("def"), &... + | ^^^^^^^^^^^^^^^^^^^^ expected `Address`, found `Symbol` + | + = note: this error originates in the macro `symbol_short` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> src/test.rs:8006:69 + | +8006 | ...nt.claim(&issuer, &symbol_short!("def"), &token, &... + | ----- ^^^^^^ expected `&Symbol`, found `&Address` + | | + | arguments to this method are incorrect + | + = note: expected reference `&soroban_sdk::Symbol` + found reference `&soroban_sdk::Address` +note: method defined here + --> src/lib.rs:2468:12 + | +2468 | pub fn claim( + | ^^^^^ +... +2472 | namespace: Symbol, + | ----------------- + +error[E0308]: mismatched types + --> src/test.rs:8015:47 + | +8015 | ..., &symbol_short!("def"), &... + | ^^^^^^^^^^^^^^^^^^^^ expected `Address`, found `Symbol` + | + = note: this error originates in the macro `symbol_short` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> src/test.rs:8015:69 + | +8015 | ...nt.claim(&issuer, &symbol_short!("def"), &token, &... + | ----- ^^^^^^ expected `&Symbol`, found `&Address` + | | + | arguments to this method are incorrect + | + = note: expected reference `&soroban_sdk::Symbol` + found reference `&soroban_sdk::Address` +note: method defined here + --> src/lib.rs:2468:12 + | +2468 | pub fn claim( + | ^^^^^ +... +2472 | namespace: Symbol, + | ----------------- + +error[E0599]: no method named `get_unclaimed_periods` found for struct `RevoraRevenueShareClient<'a>` in the current scope + --> src/test.rs:8019:34 + | +8019 | ...nt.get_unclaimed_periods(&i... + | ^^^^^^^^^^^^^^^^^^^^^ + | + ::: src/lib.rs:489:1 + | + 489 | #[contract] + | ----------- method `get_unclaimed_periods` not found for this struct + | +help: there is a method `get_pending_periods` with a similar name + | +8019 - let remaining_a = client.get_unclaimed_periods(&issuer, &symbol_short!("def"), &token, &investor_a); +8019 + let remaining_a = client.get_pending_periods(&issuer, &symbol_short!("def"), &token, &investor_a); + | + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:8052:16 + | +8052 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor, &100); + | ^^^^^^^^^^^^^^^^ -- --------- ---- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +8052 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &1, &investor, &100); +8052 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor, &1); + | + +error[E0308]: mismatched types + --> src/test.rs:8057:52 + | +8057 | ..., &symbol_short!("def"), &... + | ^^^^^^^^^^^^^^^^^^^^ expected `Address`, found `Symbol` + | + = note: this error originates in the macro `symbol_short` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> src/test.rs:8057:74 + | +8057 | ...nt.try_claim(&issuer, &symbol_short!("def"), &token, &... + | --------- ^^^^^^ expected `&Symbol`, found `&Address` + | | + | arguments to this method are incorrect + | + = note: expected reference `&soroban_sdk::Symbol` + found reference `&soroban_sdk::Address` +note: method defined here + --> src/lib.rs:2468:12 + | +2468 | pub fn claim( + | ^^^^^ +... +2472 | namespace: Symbol, + | ----------------- + +error[E0599]: no method named `set_timestamp` found for struct `Ledger` in the current scope + --> src/test.rs:8061:22 + | +8061 | ...ger().set_timestamp(env.l... + | ^^^^^^^^^^^^^ + | +help: there is a method `timestamp` with a similar name, but with different arguments + --> /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/soroban-sdk-20.5.0/src/ledger.rs:81:5 + | + 81 | pub fn timestamp(&self) -> u64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-6034050221623679165.txt' + = note: consider using `--verbose` to print the full type name to the console + +error[E0308]: mismatched types + --> src/test.rs:8070:45 + | +8070 | ..., &symbol_short!("def"), &... + | ^^^^^^^^^^^^^^^^^^^^ expected `Address`, found `Symbol` + | + = note: this error originates in the macro `symbol_short` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> src/test.rs:8070:67 + | +8070 | ...nt.claim(&issuer, &symbol_short!("def"), &token, &... + | ----- ^^^^^^ expected `&Symbol`, found `&Address` + | | + | arguments to this method are incorrect + | + = note: expected reference `&soroban_sdk::Symbol` + found reference `&soroban_sdk::Address` +note: method defined here + --> src/lib.rs:2468:12 + | +2468 | pub fn claim( + | ^^^^^ +... +2472 | namespace: Symbol, + | ----------------- + +error[E0061]: this method takes 5 arguments but 6 arguments were supplied + --> src/test.rs:8078:16 + | +8078 | ...nt.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor, &100); + | ^^^^^^^^^^^^^^^^ -- --------- ---- unexpected argument #6 of type `&{integer}` + | | | + | | expected `&u32`, found `&soroban_sdk::Address` + | expected `&soroban_sdk::Address`, found `&{integer}` + | +note: method defined here + --> src/lib.rs:2221:12 + | +2221 | pub fn set_holder_share( + | ^^^^^^^^^^^^^^^^ +help: did you mean + | +8078 - client.set_holder_share(&issuer, &symbol_short!("def"), &token, &2, &investor, &100); +8078 + client.set_holder_share(&issuer, &symbol_short!("def"), &token, &investor, &2); + | + +error[E0599]: no method named `set_timestamp` found for struct `Ledger` in the current scope + --> src/test.rs:8081:22 + | +8081 | ...ger().set_timestamp(env.l... + | ^^^^^^^^^^^^^ + | +help: there is a method `timestamp` with a similar name, but with different arguments + --> /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/soroban-sdk-20.5.0/src/ledger.rs:81:5 + | + 81 | pub fn timestamp(&self) -> u64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-6034050221623679165.txt' + = note: consider using `--verbose` to print the full type name to the console + +error[E0308]: mismatched types + --> src/test.rs:8082:60 + | +8082 | ..., &symbol_short!("def"), &... + | ^^^^^^^^^^^^^^^^^^^^ expected `Address`, found `Symbol` + | + = note: this error originates in the macro `symbol_short` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> src/test.rs:8082:82 + | +8082 | ...nt.try_claim(&issuer, &symbol_short!("def"), &token, &... + | --------- ^^^^^^ expected `&Symbol`, found `&Address` + | | + | arguments to this method are incorrect + | + = note: expected reference `&soroban_sdk::Symbol` + found reference `&soroban_sdk::Address` +note: method defined here + --> src/lib.rs:2468:12 + | +2468 | pub fn claim( + | ^^^^^ +... +2472 | namespace: Symbol, + | ----------------- + +error[E0599]: no method named `try_into_val` found for struct `soroban_sdk::Val` in the current scope + --> src/test.rs:8114:81 + | +8114 | ...rap().try_into_val(&env); + | ^^^^^^^^^^^^ + | + ::: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/soroban-env-common-20.3.0/src/convert.rs:33:8 + | + 33 | fn try_into_val(&self, e... + | ------------ the method is available for `soroban_sdk::Val` here + | + = help: items from traits can only be used if the trait is in scope +help: trait `TryIntoVal` which provides `try_into_val` is implemented but not in scope; perhaps you want to import it + | +8089 + use soroban_sdk::TryIntoVal; + | +help: there is a method `into_val` with a similar name + | +8114 - let sym: Result = topic.get(0).unwrap().try_into_val(&env); +8114 + let sym: Result = topic.get(0).unwrap().into_val(&env); + | + +error[E0599]: no method named `try_into_val` found for struct `soroban_sdk::Val` in the current scope + --> src/test.rs:8118:67 + | +8118 | ... data.try_into_val(&env).... + | ^^^^^^^^^^^^ + | + ::: /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/soroban-env-common-20.3.0/src/convert.rs:33:8 + | + 33 | fn try_into_val(&self, e... + | ------------ the method is available for `soroban_sdk::Val` here + | + = help: items from traits can only be used if the trait is in scope +help: trait `TryIntoVal` which provides `try_into_val` is implemented but not in scope; perhaps you want to import it + | +8089 + use soroban_sdk::TryIntoVal; + | +help: there is a method `into_val` with a similar name + | +8118 - let (count, threshold): (u32, u32) = data.try_into_val(&env).unwrap(); +8118 + let (count, threshold): (u32, u32) = data.into_val(&env).unwrap(); + | + +error[E0382]: use of moved value: `topics` + --> src/test.rs:6447:22 + | +6444 | ..._, topics, _) = last_event; + | ------ move occurs because `topics` has type `Vec`, which does not implement the `Copy` trait +6445 | ...opics_vec: Vec = topics; + | ------ value moved here +6446 | ...vent_symbol: Symbol = topics_vec.get(0).clon... +6447 | ...opics_vec = topics; + | ^^^^^^ value used here after move + | + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-2470963831010308036.txt' + = note: consider using `--verbose` to print the full type name to the console +help: consider cloning the value if the performance cost is acceptable + | +6445 | let topics_vec: Vec = topics.clone(); + | ++++++++ + +error[E0382]: use of moved value: `topics` + --> src/test.rs:6478:22 + | +6475 | ..._, topics, _) = last_event; + | ------ move occurs because `topics` has type `Vec`, which does not implement the `Copy` trait +6476 | ...opics_vec: Vec = topics; + | ------ value moved here +6477 | ...vent_symbol: Symbol = topics_vec.get(0).clon... +6478 | ...opics_vec = topics; + | ^^^^^^ value used here after move + | + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-2470963831010308036.txt' + = note: consider using `--verbose` to print the full type name to the console +help: consider cloning the value if the performance cost is acceptable + | +6476 | let topics_vec: Vec = topics.clone(); + | ++++++++ + +error[E0382]: use of moved value: `topics` + --> src/test.rs:6504:22 + | +6498 | ...tract, topics, data) = events.last(... + | ------ move occurs because `topics` has type `Vec`, which does not implement the `Copy` trait +... +6502 | ...: Vec = topics; + | ------ value moved here +6503 | ...ol: Symbol = topics_vec.get(0).clon... +6504 | ... = topics; + | ^^^^^^ value used here after move + | + = note: the full name for the type has been written to '/home/user/Desktop/myDesktop/March/lycantho/Revora-Contracts/target/debug/deps/revora_contracts-8c2d9717f32617ef.long-type-2470963831010308036.txt' + = note: consider using `--verbose` to print the full type name to the console +help: consider cloning the value if the performance cost is acceptable + | +6502 | let topics_vec: Vec = topics.clone(); + | ++++++++ + +Some errors have detailed explanations: E0061, E0308, E0382, E0428, E0599, E0609. +For more information about an error, try `rustc --explain E0061`. +error: could not compile `revora-contracts` (lib test) due to 30 previous errors diff --git a/test_snapshots/test/add_marks_investor_as_blacklisted.1.json b/test_snapshots/test/add_marks_investor_as_blacklisted.1.json index 16647be6c..69b1ac495 100644 --- a/test_snapshots/test/add_marks_investor_as_blacklisted.1.json +++ b/test_snapshots/test/add_marks_investor_as_blacklisted.1.json @@ -1,39 +1,14 @@ { "generators": { - "address": 4, + "address": 5, "nonce": 0 }, "auth": [ [], - [ - [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - { - "function": { - "contract_fn": { - "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "blacklist_add", - "args": [ - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" - } - ] - } - }, - "sub_invocations": [] - } - ] - ], [] ], "ledger": { - "protocol_version": 21, + "protocol_version": 20, "sequence_number": 0, "timestamp": 0, "network_id": "0000000000000000000000000000000000000000000000000000000000000000", @@ -42,109 +17,6 @@ "min_temp_entry_ttl": 16, "max_entry_ttl": 6312000, "ledger_entries": [ - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "vec": [ - { - "symbol": "Blacklist" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "vec": [ - { - "symbol": "Blacklist" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - } - ] - }, - "durability": "persistent", - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" - }, - "val": { - "bool": true - } - } - ] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "vec": [ - { - "symbol": "BlacklistOrder" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "vec": [ - { - "symbol": "BlacklistOrder" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - } - ] - }, - "durability": "persistent", - "val": { - "vec": [ - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" - } - ] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], [ { "contract_data": { @@ -177,39 +49,6 @@ 4095 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "durability": "temporary" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "durability": "temporary", - "val": "void" - } - }, - "ext": "v0" - }, - 6311999 - ] - ], [ { "contract_code": { @@ -254,11 +93,17 @@ ], "data": { "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "symbol": "def" + }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" } ] } @@ -313,11 +158,17 @@ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "symbol": "def" + }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" } ] } @@ -330,27 +181,26 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "bl_add" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "symbol": "fn_return" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "symbol": "blacklist_add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "error": { + "contract": 4 + } } } } }, - "failed_call": false + "failed_call": true }, { "event": { @@ -361,17 +211,21 @@ "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "error" }, { - "symbol": "blacklist_add" + "error": { + "contract": 4 + } } ], - "data": "void" + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } } } }, - "failed_call": false + "failed_call": true }, { "event": { @@ -382,22 +236,40 @@ "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "error" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "is_blacklisted" + "error": { + "contract": 4 + } } ], "data": { "vec": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "string": "contract call failed" + }, + { + "symbol": "blacklist_add" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "symbol": "def" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] } ] } @@ -409,20 +281,22 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "error" }, { - "symbol": "is_blacklisted" + "error": { + "contract": 4 + } } ], "data": { - "bool": true + "string": "escalating error to panic" } } }