Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ pub enum RevoraError {
/// The period has been sealed by `close_period`; no further overrides are accepted.
///
/// Wire value: 48. Stable since v1.
PeriodAlreadyClosed = 48,
PeriodAlreadyClosed = 49,
StaleConcentrationData = 50,
}

pub mod vesting;
Expand All @@ -190,6 +191,8 @@ mod test_min_revenue_threshold_boundary;
// mod test_claim_transfer_fail;
#[cfg(test)]
mod test_close_period;
#[cfg(test)]
mod test_share_conservation_prop;

// ── Event symbols ────────────────────────────────────────────
const EVENT_REVENUE_REPORTED: Symbol = symbol_short!("rev_rep");
Expand Down Expand Up @@ -266,6 +269,15 @@ pub struct Proposal {
pub expiry: u64,
}

#[contracttype]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PauseState {
NotPaused = 0,
SoftPaused = 1,
HardPaused = 2,
}


const EVENT_SNAP_CONFIG: Symbol = symbol_short!("snap_cfg");

const EVENT_INIT: Symbol = symbol_short!("init");
Expand Down Expand Up @@ -723,8 +735,6 @@ pub enum DataKey {
SnapshotFinalizationRequired,
/// Latest committed snapshot reference for an offering.
LastSnapshotCommitRef(OfferingId),
/// Whether the snapshot has been finalized successfully.
SnapshotFinalized(OfferingId, u64),
}

/// Secondary storage keys for auxiliary/extended contract state.
Expand Down Expand Up @@ -767,6 +777,13 @@ pub enum DataKey2 {

/// Sealed-period flag: when present, `report_revenue` overrides are rejected for this period.
ClosedPeriod(OfferingId, u64),

/// Whether the snapshot has been finalized successfully.
SnapshotFinalized(OfferingId, u64),

InvestmentConstraints(OfferingId),
SupplyCap(OfferingId),
MinRevenueThreshold(OfferingId),
}

/// Maximum number of offerings returned in a single page.
Expand Down Expand Up @@ -5066,7 +5083,7 @@ impl RevoraRevenueShare {
fn is_snapshot_finalized(env: &Env, offering_id: &OfferingId, snapshot_ref: u64) -> bool {
env.storage()
.persistent()
.get(&DataKey::SnapshotFinalized(offering_id.clone(), snapshot_ref))
.get(&DataKey2::SnapshotFinalized(offering_id.clone(), snapshot_ref))
.unwrap_or(false)
}

Expand Down Expand Up @@ -5140,7 +5157,7 @@ impl RevoraRevenueShare {

env.storage()
.persistent()
.set(&DataKey::SnapshotFinalized(offering_id.clone(), snapshot_ref), &true);
.set(&DataKey2::SnapshotFinalized(offering_id.clone(), snapshot_ref), &true);
env.events().publish((EVENT_SNAP_FINALIZED, issuer, namespace, token), snapshot_ref);
Ok(())
}
Expand Down
73 changes: 73 additions & 0 deletions src/test_share_conservation_prop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#![cfg(test)]

use crate::{
proptest_helpers::{any_test_operation, arb_valid_operation_sequence, TestOperation},
RevoraRevenueShare, RevoraRevenueShareClient,
};
use proptest::prelude::*;
use soroban_sdk::{testutils::Address as _, Address, Env, Symbol};

// Simple oracle for tracking expected holder shares
fn verify_share_conservation(
env: &Env,
client: &RevoraRevenueShareClient,
issuer: &Address,
namespace: &Symbol,
token: &Address,
holders: &[Address],
) {
let mut total_bps = 0u32;
for holder in holders {
total_bps += client.get_holder_share(issuer, namespace, token, holder);
}
assert!(
total_bps <= 10_000,
"Share conservation violated! Total BPS = {}",
total_bps
);
}

proptest! {
#![proptest_config(ProptestConfig {
cases: 10_000,
max_local_rng: None,
..ProptestConfig::default()
})]

#[test]
fn prop_share_conservation(env in Env::default(), seq in arb_valid_operation_sequence(20usize)) {
env.mock_all_auths();
let contract_id = env.register_contract(None, RevoraRevenueShare);
let client = RevoraRevenueShareClient::new(&env, &contract_id);

let admin = Address::generate(&env);
client.initialize(&admin, &None::<Address>, &None::<bool>);

// Track generated issuers, tokens and holders so we can query them
let mut active_offerings: Vec<(Address, Symbol, Address)> = vec![];
let mut all_holders: Vec<Address> = vec![];

for op in seq {
match op {
TestOperation::RegisterOffering { issuer, namespace, token, bps, payout_asset, supply_cap } => {
if let Ok(_) = client.try_register_offering(&issuer, &namespace, &token, &bps, &payout_asset, &supply_cap) {
active_offerings.push((issuer, namespace, token));
}
}
TestOperation::SetHolderShare { issuer, namespace, token, holder, share_bps } => {
let _ = client.try_set_holder_share(&issuer, &namespace, &token, &holder, &share_bps);
if !all_holders.contains(&holder) {
all_holders.push(holder);
}
}
// Add execution for other ops if they exist in TestOperation...
_ => {}
}

// Assert invariant after each successful op
for (i, ns, t) in &active_offerings {
verify_share_conservation(&env, &client, i, ns, t, &all_holders);
}
}
}
}
Loading