@@ -10,72 +10,177 @@ use soroban_sdk::{
1010// Issue #109 — Revenue report correction workflow with audit trail.
1111// Placeholder branch for upstream PR scaffolding; full implementation in follow-up.
1212
13- /// Centralized contract error codes. Auth failures are signaled by host panic (require_auth).
13+ /// Centralized contract error codes.
14+ ///
15+ /// All state-mutating entrypoints return `Result<_, RevoraError>` so callers can
16+ /// distinguish contract-level rejections from host-level auth panics. Use the
17+ /// `try_*` client methods to receive these as `Result`.
18+ ///
19+ /// Auth failures (wrong signer) are signaled by host panic, not `RevoraError`.
20+ ///
21+ /// # Numeric stability
22+ /// Each variant's discriminant is fixed and must never be renumbered; integrators
23+ /// may store or transmit the raw `u32` value.
1424#[ contracterror]
1525#[ derive( Copy , Clone , Debug , Eq , PartialEq , Ord , PartialOrd ) ]
1626#[ repr( u32 ) ]
1727pub enum RevoraError {
18- /// revenue_share_bps exceeded 10000 (100%).
28+ /// `register_offering`: `revenue_share_bps` > 10 000 (100 %).
29+ ///
30+ /// Testnet mode bypasses this check to allow flexible testing.
31+ /// Discriminant: 1.
1932 InvalidRevenueShareBps = 1 ,
20- /// Reserved for future use (e.g. offering limit per issuer).
33+
34+ /// General guard for operations that are structurally disallowed in the
35+ /// current contract state (e.g. admin already set, multisig already
36+ /// initialized, threshold out of range, fee above maximum).
37+ ///
38+ /// Also returned by `set_platform_fee` / `set_offering_fee_bps` when
39+ /// `fee_bps > 5 000`.
40+ /// Discriminant: 2.
2141 LimitReached = 2 ,
22- /// Holder concentration exceeds configured limit and enforcement is enabled.
42+
43+ /// `report_revenue`: the last reported single-holder concentration exceeds
44+ /// the configured `max_bps` limit **and** `enforce` is `true`.
45+ ///
46+ /// Call `report_concentration` to update the stored value, or lower the
47+ /// limit via `set_concentration_limit`.
48+ /// Discriminant: 3.
2349 ConcentrationLimitExceeded = 3 ,
24- /// No offering found for the given (issuer, token) pair.
50+
51+ /// The requested `(issuer, namespace, token)` triple has no registered
52+ /// offering, or the caller is not the current issuer of that offering.
53+ ///
54+ /// Returned by any issuer-gated entrypoint when the offering lookup fails.
55+ /// Discriminant: 4.
2556 OfferingNotFound = 4 ,
26- /// Revenue already deposited for this period.
57+
58+ /// `deposit_revenue`: revenue has already been deposited for this
59+ /// `period_id`. Each period may only be deposited once.
60+ /// Discriminant: 5.
2761 PeriodAlreadyDeposited = 5 ,
28- /// No unclaimed periods for this holder.
62+
63+ /// `claim`: the holder has no share allocated (`share_bps == 0`) or all
64+ /// deposited periods have already been claimed.
65+ /// Discriminant: 6.
2966 NoPendingClaims = 6 ,
30- /// Holder is blacklisted for this offering.
67+
68+ /// `claim`: the holder is on the per-offering blacklist and cannot receive
69+ /// revenue. Blacklisted holders retain their `share_bps` but cannot call
70+ /// `claim` until removed from the blacklist.
71+ /// Discriminant: 7.
3172 HolderBlacklisted = 7 ,
32- /// Holder share_bps exceeded 10000 (100%).
73+
74+ /// `set_holder_share`: `share_bps` > 10 000 (100 %).
75+ /// Discriminant: 8.
3376 InvalidShareBps = 8 ,
34- /// Payment token does not match previously set token for this offering.
77+
78+ /// `deposit_revenue`: the supplied `payment_token` differs from the token
79+ /// locked on the first deposit for this offering. The payment token is
80+ /// immutable after the first deposit.
81+ /// Discriminant: 9.
3582 PaymentTokenMismatch = 9 ,
36- /// Contract is frozen; state-changing operations are disabled.
83+
84+ /// The contract is frozen; all state-mutating operations are disabled.
85+ ///
86+ /// Read-only queries and `claim` remain available. Unfreeze requires a
87+ /// new deployment or multisig action (depending on configuration).
88+ /// Discriminant: 10.
3789 ContractFrozen = 10 ,
38- /// Revenue for this period is not yet claimable (delay not elapsed).
90+
91+ /// `claim`: the next claimable period has not yet passed the configured
92+ /// `ClaimDelaySecs` window. The caller should retry after the delay
93+ /// elapses.
94+ /// Discriminant: 11.
3995 ClaimDelayNotElapsed = 11 ,
4096
41- /// Snapshot distribution is not enabled for this offering.
97+ /// `deposit_revenue_with_snapshot`: snapshot-based distribution is not
98+ /// enabled for this offering. Call `set_snapshot_config(true)` first.
99+ /// Discriminant: 12.
42100 SnapshotNotEnabled = 12 ,
43101 /// Provided snapshot reference is outdated or duplicates a previous one.
44102 /// Overriding an existing revenue report.
45103 OutdatedSnapshot = 13 ,
46104 /// Payout asset mismatch.
47105 PayoutAssetMismatch = 14 ,
48- /// A transfer is already pending for this offering.
106+
107+ /// `propose_issuer_transfer`: a transfer is already pending for this
108+ /// offering. Cancel the existing proposal before proposing a new one.
109+ /// Discriminant: 15.
49110 IssuerTransferPending = 15 ,
50- /// No transfer is pending for this offering.
111+
112+ /// `accept_issuer_transfer` / `cancel_issuer_transfer`: no transfer is
113+ /// currently pending for this offering.
114+ /// Discriminant: 16.
51115 NoTransferPending = 16 ,
52- /// Caller is not authorized to accept this transfer.
116+
117+ /// `accept_issuer_transfer`: the caller is not the address that was
118+ /// nominated as the new issuer in the pending transfer proposal.
119+ ///
120+ /// Security note: this is a typed error rather than a host panic so that
121+ /// callers can distinguish "wrong acceptor" from "no pending transfer".
122+ /// Discriminant: 17.
53123 UnauthorizedTransferAccept = 17 ,
54- /// Metadata string exceeds maximum allowed length.
124+
125+ /// `set_offering_metadata`: the metadata string exceeds
126+ /// `MAX_METADATA_LENGTH` (256 bytes).
127+ /// Discriminant: 18.
55128 MetadataTooLarge = 18 ,
56- /// Caller is not authorized to perform this action.
129+
130+ /// `meta_set_holder_share` / `meta_approve_revenue_report`: the signer is
131+ /// not the configured delegate for this offering.
132+ /// Discriminant: 19.
57133 NotAuthorized = 19 ,
58- /// Contract is not initialized (admin not set).
134+
135+ /// A required admin address has not been set.
136+ ///
137+ /// Returned by `require_admin` when `DataKey::Admin` is absent. This
138+ /// indicates the contract was not properly initialized before use.
139+ /// Discriminant: 20.
59140 NotInitialized = 20 ,
60- /// Amount is invalid (e.g. negative for deposit, or out of allowed range) (#35).
141+
142+ /// `report_revenue` / `set_min_revenue_threshold`: `amount` is negative.
143+ /// `deposit_revenue`: `amount` <= 0.
144+ /// `set_investment_constraints`: `min_stake` or `max_stake` is negative.
145+ /// Discriminant: 21.
61146 InvalidAmount = 21 ,
62147 /// period_id is invalid (e.g. zero when required to be positive) (#35).
63148 /// period_id not strictly greater than previous (violates ordering invariant).
64149 InvalidPeriodId = 22 ,
65150
66151 /// Deposit would exceed the offering's supply cap (#96).
67152 SupplyCapExceeded = 23 ,
68- /// Metadata format is invalid for configured scheme rules.
153+
154+ /// `set_offering_metadata`: the metadata string does not start with a
155+ /// recognised scheme prefix (`ipfs://`, `https://`, `ar://`, `sha256:`).
156+ /// Discriminant: 24.
69157 MetadataInvalidFormat = 24 ,
70- /// Current ledger timestamp is outside configured reporting window.
158+
159+ /// `report_revenue`: the current ledger timestamp is outside the
160+ /// configured reporting window for this offering.
161+ /// Discriminant: 25.
71162 ReportingWindowClosed = 25 ,
72- /// Current ledger timestamp is outside configured claiming window.
163+
164+ /// `claim`: the current ledger timestamp is outside the configured
165+ /// claiming window for this offering.
166+ /// Discriminant: 26.
73167 ClaimWindowClosed = 26 ,
74- /// Off-chain signature has expired.
168+
169+ /// `meta_set_holder_share` / `meta_approve_revenue_report`: the
170+ /// off-chain signature's `expiry` timestamp is in the past.
171+ /// Discriminant: 27.
75172 SignatureExpired = 27 ,
76- /// Signature nonce has already been used.
173+
174+ /// `meta_set_holder_share` / `meta_approve_revenue_report`: the nonce
175+ /// has already been consumed. Each nonce may only be used once per
176+ /// signer to prevent replay attacks.
177+ /// Discriminant: 28.
77178 SignatureReplay = 28 ,
78- /// Off-chain signer key has not been registered.
179+
180+ /// `meta_set_holder_share` / `meta_approve_revenue_report`: no ed25519
181+ /// public key has been registered for the signer address. Call
182+ /// `register_meta_signer_key` first.
183+ /// Discriminant: 29.
79184 SignerKeyNotRegistered = 29 ,
80185 /// Cross-contract token transfer failed.
81186 TransferFailed = 30 ,
@@ -4581,6 +4686,12 @@ impl RevoraRevenueShare {
45814686 }
45824687
45834688 /// Accept a pending issuer transfer. Only the proposed new issuer may call this.
4689+ ///
4690+ /// # Parameters
4691+ /// - `caller`: The address attempting to accept the transfer. Must match
4692+ /// the address nominated in `propose_issuer_transfer`; otherwise returns
4693+ /// `Err(UnauthorizedTransferAccept)`.
4694+ /// - `issuer`: The current (old) issuer, used to locate the offering.
45844695 pub fn accept_issuer_transfer (
45854696 env : Env ,
45864697 caller : Address ,
@@ -5215,6 +5326,9 @@ impl RevoraRevenueShare {
52155326 // ── Testnet mode configuration (#24) ───────────────────────
52165327
52175328 /// Enable or disable testnet mode. Only admin may call.
5329+ ///
5330+ /// Returns `Err(NotInitialized)` if the contract admin has not been set yet,
5331+ /// allowing callers to distinguish "not initialized" from other auth failures.
52185332 /// When enabled, certain validations are relaxed for testnet deployments.
52195333 /// Emits event with new mode state.
52205334 pub fn set_testnet_mode ( env : Env , enabled : bool ) -> Result < ( ) , RevoraError > {
0 commit comments