From 20b70f0daf4b2cb0a333ea7647066aa0856c4291 Mon Sep 17 00:00:00 2001 From: Umeokonkwo Samuel Date: Mon, 1 Jun 2026 07:51:04 +0100 Subject: [PATCH] Implement MissingReportForOverride override-missing event and add regression test --- README.md | 2 ++ src/lib.rs | 25 +++++++++++++++++++++++++ src/test_duplicates.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/README.md b/README.md index d831d635..0cbea410 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Soroban contract for revenue-share offerings and blacklist management. | 18 | `InvalidPeriodId` | period_id is 0 where a positive value is required (#35). | | 25 | `ReportingWindowClosed` | Current ledger timestamp is outside the configured reporting window; `report_revenue` rejected. | | 26 | `ClaimWindowClosed` | Current ledger timestamp is outside the configured claiming window; `claim` rejected. | +| 47 | `MissingReportForOverride` | `report_revenue` rejected when `override_existing=true` is requested for a period that has no existing persisted report. Emits `rev_omiss`. | Auth failures (e.g. wrong signer) are signaled by host/panic, not `RevoraError`. Use `try_register_offering`, `try_report_revenue`, and similar `try_*` client methods to receive contract errors as `Result`. @@ -86,6 +87,7 @@ Auth failures (e.g. wrong signer) are signaled by host/panic, not `RevoraError`. | `rev_init` | `(issuer, token), (amount, period_id, blacklist_vec)` | First persisted report for a period. | | `rev_ovrd` | `(issuer, token), (new_amount, period_id, old_amount, blacklist_vec)` | Accepted correction of an existing persisted period (`override_existing=true`). | | `rev_rej` | `(issuer, token), (attempted_amount, period_id, existing_amount, blacklist_vec)` | Duplicate report attempt for an existing period when `override_existing=false`; no state change. | +| `rev_omiss` | (`issuer, token`), (`attempted_amount, period_id`) | Rejected override attempt when `override_existing=true` is used for a period with no prior report. | | `rev_rep` | `(issuer, token), (amount, period_id, blacklist_vec)` | Receipt for an accepted persisted report call (initial or override). Use `rev_init` plus `rev_ovrd` to reconstruct audit totals. | | `bl_add` | `(token, caller), investor` | After `blacklist_add`. | | `bl_rem` | `(token, caller), investor` | After `blacklist_remove`. | diff --git a/src/lib.rs b/src/lib.rs index a4eceed9..c14b70b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,6 +215,7 @@ const EVENT_REVENUE_REPORT_INITIAL_ASSET: Symbol = symbol_short!("rev_inia"); const EVENT_REVENUE_REPORT_OVERRIDE: Symbol = symbol_short!("rev_ovrd"); const EVENT_REVENUE_REPORT_OVERRIDE_ASSET: Symbol = symbol_short!("rev_ovra"); const EVENT_REVENUE_REPORT_REJECTED: Symbol = symbol_short!("rev_rej"); +const EVENT_REVENUE_REPORT_MISSING_OVERRIDE: Symbol = symbol_short!("rev_omiss"); const EVENT_REVENUE_REPORT_REJECTED_ASSET: Symbol = symbol_short!("rev_reja"); pub const EVENT_SCHEMA_VERSION_V2: u32 = 2; @@ -299,6 +300,7 @@ const EVENT_TYPE_OFFER: Symbol = symbol_short!("offer"); const EVENT_TYPE_REV_INIT: Symbol = symbol_short!("rv_init"); const EVENT_TYPE_REV_OVR: Symbol = symbol_short!("rv_ovr"); const EVENT_TYPE_REV_REJ: Symbol = symbol_short!("rv_rej"); +const EVENT_TYPE_REV_OMISS: Symbol = symbol_short!("rv_omiss"); const EVENT_TYPE_REV_REP: Symbol = symbol_short!("rv_rep"); const EVENT_TYPE_CLAIM: Symbol = symbol_short!("claim"); const EVENT_REPORT_WINDOW_SET: Symbol = symbol_short!("rep_win"); @@ -2610,6 +2612,29 @@ impl RevoraRevenueShare { } None => { if override_existing { + env.events().publish( + ( + EVENT_REVENUE_REPORT_MISSING_OVERRIDE, + issuer.clone(), + namespace.clone(), + token.clone(), + ), + (amount, period_id), + ); + env.events().publish( + ( + EVENT_INDEXED_V2, + EventIndexTopicV2 { + version: 2, + event_type: EVENT_TYPE_REV_OMISS, + issuer: issuer.clone(), + namespace: namespace.clone(), + token: token.clone(), + period_id, + }, + ), + (amount, period_id, payout_asset.clone()), + ); return Err(RevoraError::MissingReportForOverride); } // preserve existing initial-report behavior when override_existing=false diff --git a/src/test_duplicates.rs b/src/test_duplicates.rs index 971fd108..abf27e83 100644 --- a/src/test_duplicates.rs +++ b/src/test_duplicates.rs @@ -224,3 +224,41 @@ fn test_duplicate_report_revenue_rev_rej_event_payload() { } assert!(found_asset, "rev_reja event with correct payload must be emitted"); } + +#[test] +fn test_override_missing_report_emits_rev_omiss_and_returns_missing_override() { + let (env, client, issuer, token, payout_asset) = setup_offering(); + let namespace = symbol_short!("ns"); + let attempted_amount: i128 = 123; + let period_id: u64 = 1; + + let before = env.events().all().len(); + let result = client.try_report_revenue( + &issuer, + &namespace, + &token, + &payout_asset, + &attempted_amount, + &period_id, + &true, + ); + + assert_eq!(result, Err(Ok(RevoraError::MissingReportForOverride))); + + let mut found_rev_omiss = false; + for i in before..env.events().all().len() { + let (_, topics, data) = env.events().all().get(i).unwrap(); + let topics_vec: soroban_sdk::Vec = topics.clone().into_val(&env); + let topic_sym: Symbol = topics_vec.get(0).unwrap().into_val(&env); + if topic_sym == symbol_short!("rev_omiss") { + let data_vec: soroban_sdk::Vec = data.clone().into_val(&env); + let amount: i128 = data_vec.get(0).unwrap().into_val(&env); + let pid: u64 = data_vec.get(1).unwrap().into_val(&env); + assert_eq!(amount, attempted_amount); + assert_eq!(pid, period_id); + found_rev_omiss = true; + break; + } + } + assert!(found_rev_omiss, "rev_omiss event with correct payload must be emitted"); +}