Skip to content

Commit eb0a1dc

Browse files
authored
Merge pull request #220 from devJaja/feature/contracts-046-structured-error-coverage-expansion
Feature/contracts 046 structured error coverage expansion
2 parents f777c4d + 02bca0f commit eb0a1dc

2 files changed

Lines changed: 140 additions & 482 deletions

File tree

src/lib.rs

Lines changed: 140 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
1727
pub 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

Comments
 (0)