@@ -1705,9 +1705,11 @@ impl RevoraRevenueShare {
17051705 }
17061706 // Optionally emit versioned v1 events for forward-compatible consumers
17071707 if Self :: is_event_versioning_enabled ( env. clone ( ) ) {
1708- env. events ( ) . publish (
1709- ( EVENT_REV_INIT_V1 , issuer. clone ( ) , namespace. clone ( ) , token. clone ( ) ) ,
1710- ( EVENT_SCHEMA_VERSION , amount, period_id, blacklist. clone ( ) ) ,
1708+ // Versioned event v2: [version: u32, payout_asset: Address, amount: i128, period_id: u64, blacklist: Vec<Address>]
1709+ Self :: emit_v2_event (
1710+ & env,
1711+ ( EVENT_REV_INIA_V2 , issuer. clone ( ) , namespace. clone ( ) , token. clone ( ) ) ,
1712+ ( payout_asset. clone ( ) , amount, period_id, blacklist. clone ( ) ) ,
17111713 ) ;
17121714 }
17131715
@@ -2594,7 +2596,13 @@ impl RevoraRevenueShare {
25942596 }
25952597
25962598 /// Compute share of `amount` at `revenue_share_bps` using the given rounding mode.
2597- /// Guarantees: result between 0 and amount (inclusive); no loss of funds when summing shares if caller uses same mode.
2599+ /// Security assumptions:
2600+ /// - Callers should pass `revenue_share_bps` in [0, 10_000]. Values above 10_000 are rejected by returning 0.
2601+ /// - Revenue flows in this contract are non-negative, but this helper is total over signed `amount` for testability.
2602+ ///
2603+ /// Guarantees:
2604+ /// - Overflow-resistant arithmetic without panic.
2605+ /// - Result is clamped to [min(0, amount), max(0, amount)] to avoid over-distribution.
25982606 pub fn compute_share (
25992607 _env : Env ,
26002608 amount : i128 ,
@@ -2604,17 +2612,45 @@ impl RevoraRevenueShare {
26042612 if revenue_share_bps > 10_000 {
26052613 return 0 ;
26062614 }
2615+ if amount == 0 || revenue_share_bps == 0 {
2616+ return 0 ;
2617+ }
2618+
2619+ // Decompose `amount` to avoid `amount * bps` overflow:
2620+ // amount = q * 10_000 + r, so (amount * bps) / 10_000 = q * bps + (r * bps) / 10_000.
2621+ // `r` is bounded to (-10_000, 10_000), so `r * bps` is always safe in i128.
2622+ let q = amount / 10_000 ;
2623+ let r = amount % 10_000 ;
26072624 let bps = revenue_share_bps as i128 ;
2608- let raw = amount. checked_mul ( bps) . unwrap_or ( 0 ) ;
2609- let share = match mode {
2610- RoundingMode :: Truncation => raw. checked_div ( 10_000 ) . unwrap_or ( 0 ) ,
2625+ let base = q. checked_mul ( bps) . unwrap_or_else ( || {
2626+ if ( q >= 0 && bps >= 0 ) || ( q < 0 && bps < 0 ) {
2627+ i128:: MAX
2628+ } else {
2629+ i128:: MIN
2630+ }
2631+ } ) ;
2632+
2633+ let remainder_product = r * bps;
2634+ let remainder_share = match mode {
2635+ RoundingMode :: Truncation => remainder_product / 10_000 ,
26112636 RoundingMode :: RoundHalfUp => {
26122637 let half = 5_000_i128 ;
2613- let adjusted =
2614- if raw >= 0 { raw. saturating_add ( half) } else { raw. saturating_sub ( half) } ;
2615- adjusted. checked_div ( 10_000 ) . unwrap_or ( 0 )
2638+ if remainder_product >= 0 {
2639+ remainder_product. saturating_add ( half) / 10_000
2640+ } else {
2641+ remainder_product. saturating_sub ( half) / 10_000
2642+ }
26162643 }
26172644 } ;
2645+
2646+ let share = base. checked_add ( remainder_share) . unwrap_or_else ( || {
2647+ if ( base >= 0 && remainder_share >= 0 ) || ( base < 0 && remainder_share < 0 ) {
2648+ if base >= 0 { i128:: MAX } else { i128:: MIN }
2649+ } else {
2650+ 0
2651+ }
2652+ } ) ;
2653+
26182654 // Clamp to [min(0, amount), max(0, amount)] to avoid overflow semantics affecting bounds
26192655 let lo = core:: cmp:: min ( 0 , amount) ;
26202656 let hi = core:: cmp:: max ( 0 , amount) ;
0 commit comments