forked from InsurNiffy/niff-Stellar-shurance
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.rs
More file actions
532 lines (495 loc) · 20.1 KB
/
Copy pathtypes.rs
File metadata and controls
532 lines (495 loc) · 20.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
use soroban_sdk::{contractevent, contracttype, Address, Bytes, Map, String, Vec};
// ── Field size limits ─────────────────────────────────────────────────────────
pub const DETAILS_MAX_LEN: u32 = 256;
pub const IMAGE_URL_MAX_LEN: u32 = 128;
pub const IMAGE_URLS_MAX: u32 = 5;
pub const REASON_MAX_LEN: u32 = 128;
pub const SAFETY_SCORE_MAX: u32 = 100;
// ── Rejection side-effect thresholds ─────────────────────────────────────────
//
// GOVERNANCE NOTE: This constant is the only on-chain parameter controlling
// automatic policy deactivation. Changing it requires a contract upgrade and
// cannot be altered by the admin at runtime — removing an avenue for
// admin-only extraction via strike-count manipulation.
//
// LEGAL NOTE: Product/legal must sign off on the strike threshold before
// mainnet deployment. Three rejections is a conservative starting point.
// The threshold intentionally errs toward coverage preservation; false
// positives (legitimate holders de-activated) are harder to recover from
// than false negatives (fraudulent holders retained until human review).
//
// APPEAL INTERACTION: If an appeal window is added later, auto-deactivation
// should be deferred until the appeal deadline passes. Implement by adding a
// `deactivation_pending_until_ledger: u32` field to Policy and skipping the
// `is_active = false` write until that ledger is reached.
// ── Ledger window constants (re-exported from ledger.rs for ABI visibility) ───
//
// These are the canonical values used by on-chain checks. The frontend and
// backend MUST import from here (or the generated contract spec) rather than
// hard-coding their own values.
//
// Conversion: 1 ledger ≈ 5 s on Stellar Mainnet (Protocol 20+).
// See: https://developers.stellar.org/docs/learn/fundamentals/stellar-consensus-protocol
pub use crate::ledger::{
APPEAL_OPEN_WINDOW_LEDGERS, APPEAL_VOTE_WINDOW_LEDGERS, LEDGERS_PER_DAY, LEDGERS_PER_HOUR,
LEDGERS_PER_MIN, LEDGERS_PER_WEEK, MAX_APPEALS_PER_CLAIM, POLICY_DURATION_LEDGERS,
QUOTE_TTL_LEDGERS, RATE_LIMIT_WINDOW_LEDGERS, RENEWAL_WINDOW_LEDGERS, SECS_PER_LEDGER,
VOTE_WINDOW_LEDGERS,
};
// ── Strike / rejection constants ──────────────────────────────────────────────
/// Number of rejected claims that automatically deactivates a policy.
///
/// This is a **compile-time constant**, not a runtime admin parameter. Admin
/// cannot flip it post-deployment, which prevents governance gaming where a
/// large voter bloc rejects claims to deactivate rival policies.
///
/// **Legal review:** Before changing this value, consult legal counsel on
/// whether automatic policy cancellation triggers regulatory requirements
/// (e.g., notice periods, appeal rights).
///
/// **Appeal interaction:** Deactivation triggered by reaching this threshold
/// can be reversed by a successful appeal that decrements strikes back below it.
pub const STRIKE_DEACTIVATION_THRESHOLD: u32 = 3;
// ── Enums ─────────────────────────────────────────────────────────────────────
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum PolicyType {
Auto,
Health,
Property,
}
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum RegionTier {
Low,
Medium,
High,
}
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum AgeBand {
Young,
Adult,
Senior,
}
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum CoverageType {
Basic,
Standard,
Premium,
}
/// Claim lifecycle state machine.
///
/// Base-flow transitions:
/// Processing → Approved (majority approve vote or deadline plurality)
/// Processing → Rejected (majority reject vote or deadline plurality/tie)
/// Approved → Paid (admin calls process_claim)
///
/// Appeal-flow transitions (requires Rejected status + open appeal window):
/// Rejected → UnderAppeal (claimant calls open_appeal within window)
/// UnderAppeal → AppealApproved (majority approve appeal vote or deadline)
/// UnderAppeal → AppealRejected (majority reject appeal vote or deadline)
/// AppealApproved → Paid (admin calls process_claim — same as Approved)
///
/// Terminal states (no further transitions): Paid, Rejected (after appeal window
/// closes), AppealApproved (→ Paid only), AppealRejected.
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ClaimStatus {
Processing,
Pending,
Approved,
Paid,
Rejected,
/// Claimant has opened an appeal; fresh vote round in progress.
UnderAppeal,
/// Appeal vote resolved in claimant's favour; awaits admin payout.
AppealApproved,
/// Appeal vote rejected; claim is permanently closed.
AppealRejected,
}
impl ClaimStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
ClaimStatus::Approved
| ClaimStatus::Paid
| ClaimStatus::Rejected
| ClaimStatus::AppealApproved
| ClaimStatus::AppealRejected
)
}
}
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum VoteOption {
Approve,
Reject,
}
/// Reason for policy termination.
///
/// GOVERNANCE NOTE: `ExcessiveRejections` is set by the claims engine
/// automatically when `strike_count` reaches `STRIKE_DEACTIVATION_THRESHOLD`.
/// All other variants require an explicit holder or admin action.
///
/// CENTRALIZATION RISK: `AdminOverride` allows the admin to terminate any
/// policy for any reason at any time. This is a privileged operation that
/// bypasses normal holder protections. Consider a time-lock or multi-sig
/// requirement before using this variant in production.
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TerminationReason {
None,
VoluntaryCancellation,
LapsedNonPayment,
UnderwritingVoid,
FraudOrMisrepresentation,
RegulatoryAction,
AdminOverride,
/// Automatically set when `Policy.strike_count` reaches
/// `STRIKE_DEACTIVATION_THRESHOLD` consecutive rejections.
/// No admin intervention is required or possible to prevent this;
/// the transition is deterministic and trustless.
///
/// APPEAL NOTE: If an appeal window is introduced, deactivation should be
/// deferred until the appeal window closes. The `PolicyDeactivated` event
/// (emitted in `claim.rs`) is the authoritative signal for indexers; it
/// will carry a `reason_code = 1` identifying this variant.
ExcessiveRejections,
}
// ── Pagination ────────────────────────────────────────────────────────────────
/// Hard cap on items returned per paginated call.
///
/// Soroban charges per-entry read fees; returning more than this in a single
/// simulation would blow the default instruction budget. Callers requesting
/// a larger `limit` receive exactly `PAGE_SIZE_MAX` items — never an error.
///
/// Ordering: policies are returned in ascending `policy_id` order; claims in
/// ascending `claim_id` order. Both orderings are stable across calls as long
/// as no items are deleted (items are never deleted in this contract).
///
/// Stale-cursor note: cursors are plain integer offsets (policy_id / claim_id).
/// If the underlying counter has not changed between pages, the cursor is safe.
/// Because IDs are monotonically increasing and records are never deleted,
/// a cursor pointing past the last item simply returns an empty page — it
/// never panics or skips records.
pub const PAGE_SIZE_MAX: u32 = 20;
/// Lightweight policy summary returned by `list_policies`.
///
/// Omits large or rarely-needed fields (`details`, `image_urls`, etc.) to keep
/// per-page byte cost predictable. Callers that need the full record should
/// follow up with `get_policy(holder, policy_id)`.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicySummary {
pub policy_id: u32,
pub policy_type: PolicyType,
pub coverage: i128,
pub is_active: bool,
pub end_ledger: u32,
}
/// Lightweight claim summary returned by `list_claims`.
///
/// Omits `details` and `image_urls` to keep per-page byte cost predictable.
/// Callers that need the full record should follow up with `get_claim(claim_id)`.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClaimSummary {
pub claim_id: u64,
pub policy_id: u32,
pub amount: i128,
pub status: ClaimStatus,
pub filed_at: u32,
}
// ── Premium engine structs ────────────────────────────────────────────────────
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RiskInput {
pub region: RegionTier,
pub age_band: AgeBand,
pub coverage: CoverageType,
pub safety_score: u32,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MultiplierTable {
pub region: Map<RegionTier, i128>,
pub age: Map<AgeBand, i128>,
pub coverage: Map<CoverageType, i128>,
pub safety_discount: i128,
pub version: u32,
}
#[contractevent(topics = ["niffyinsure", "premium_table_updated"])]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PremiumTableUpdated {
pub version: u32,
}
#[contractevent(topics = ["niffyinsure", "claim_paid"])]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClaimProcessed {
#[topic]
pub claim_id: u64,
pub recipient: Address,
pub amount: i128,
}
// ── Core structs ──────────────────────────────────────────────────────────────
#[contracttype]
#[derive(Clone)]
pub struct Policy {
pub holder: Address,
pub policy_id: u32,
pub policy_type: PolicyType,
pub region: RegionTier,
pub premium: i128,
pub coverage: i128,
pub is_active: bool,
pub start_ledger: u32,
pub end_ledger: u32,
/// SEP-41 asset contract used for this policy's premium payment and claim payout.
/// Must be allowlisted at the time of policy initiation.
pub asset: Address,
// Termination fields
pub terminated_at_ledger: u32,
pub termination_reason: TerminationReason,
pub terminated_by_admin: bool,
/// Running count of rejected claims against this policy.
///
/// Incremented by `claim::on_reject` every time a claim on this policy
/// reaches `ClaimStatus::Rejected` (whether via majority vote or deadline
/// finalization). Never decremented; exists purely for accumulation.
///
/// When `strike_count >= STRIKE_DEACTIVATION_THRESHOLD`, the policy is
/// automatically deactivated (`is_active = false`) and the
/// `PolicyDeactivated` event is emitted. No admin action is required.
///
/// RENEWAL GATE: Any future `renew_policy` implementation MUST check
/// `strike_count` before allowing renewal. A policy with strikes at or
/// near the threshold should be blocked or require admin review.
///
/// DATA VISIBILITY: This field is stored on-chain and permanently
/// readable via `get_policy`. It carries only a count — no allegation
/// narratives, no claimant-identifying data.
pub strike_count: u32,
}
/// On-chain claim record.
///
/// `filed_at` is the ledger sequence at which the claim was filed. It anchors
/// the voting deadline: votes are accepted while `now < filed_at + VOTE_WINDOW_LEDGERS`.
#[contracttype]
#[derive(Clone)]
pub struct Claim {
pub claim_id: u64,
pub policy_id: u32,
pub claimant: Address,
pub amount: i128,
pub details: String,
pub image_urls: Vec<String>,
pub status: ClaimStatus,
pub voting_deadline_ledger: u32,
pub approve_votes: u32,
pub reject_votes: u32,
/// Ledger sequence at which this claim was filed (voting window anchor).
pub filed_at: u32,
// ── Appeal fields ────────────────────────────────────────────────────────
/// Ledger by which `open_appeal` must be called (0 if never rejected).
/// Set to `rejected_at + APPEAL_OPEN_WINDOW_LEDGERS` when status → Rejected.
pub appeal_open_deadline_ledger: u32,
/// How many appeals have been opened for this claim (cap = MAX_APPEALS_PER_CLAIM).
pub appeals_count: u32,
/// Voting deadline for the current appeal round (0 if no appeal open).
pub appeal_deadline_ledger: u32,
/// Approve votes cast in the current appeal round.
pub appeal_approve_votes: u32,
/// Reject votes cast in the current appeal round.
pub appeal_reject_votes: u32,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PremiumQuoteLineItem {
pub component: String,
pub factor: i128,
pub amount: i128,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PremiumQuote {
pub total_premium: i128,
pub line_items: Option<Vec<PremiumQuoteLineItem>>,
pub valid_until_ledger: u32,
pub config_version: u32,
}
// ═══════════════════════════════════════════════════════════════════════════════
// ORACLE / PARAMETRIC TRIGGER STUBS
//
// ⚠️ LEGAL / COMPLIANCE REVIEW GATE: This module contains non-active scaffolding
// for parametric insurance automation. Do NOT activate in production without:
// • Completed regulatory classification review (parametric vs indemnity)
// • Legal review of smart contract-triggered payouts
// • Game-theoretic analysis of oracle incentivization
// • Cryptographic design review for signature verification
//
// Compilation guarded by `#[cfg(feature = "experimental")]`. Default builds
// are cryptographically unable to process oracle triggers (stub panics ensure
// this at compile time).
// ═══════════════════════════════════════════════════════════════════════════════
/// Placeholder enum for oracle data source types.
///
/// Once a cryptographic design is finalized, this will define trusted
/// attestation sources (e.g., weather APIs, flight trackers, price feeds).
///
/// CRYPTOGRAPHIC DESIGN NOTE:
/// Any signature verification scheme must be reviewed before activation.
/// Known concerns to resolve:
/// - Replay attack prevention (nonce management)
/// - Oracle key rotation mechanism
/// - Sybil resistance (how to prevent fake oracles)
/// - Collusion detection
#[contracttype]
#[derive(Clone, PartialEq, Debug)]
pub enum OracleSource {
/// Stub: no trusted source defined yet.
Undefined,
// Future variants (examples only — NOT implemented):
// WeatherStation(Address),
// FlightTracker(Address),
// PriceFeed { asset: String, threshold: i128 },
// MultiSigOracle(Vec<Address>),
}
/// Placeholder enum for trigger event types.
///
/// These represent conditions under which parametric claims may auto-trigger.
/// Each variant should have associated validation rules defined in
/// `DESIGN-ORACLE.md` before implementation.
///
/// GAME-THEORETIC REQUIREMENTS (to be documented):
/// - How are oracles incentivized to report truthfully?
/// - What slash conditions exist for malicious reports?
/// - How is consensus achieved for ambiguous events (e.g., "storm damage")?
#[contracttype]
#[derive(Clone, PartialEq, Debug)]
pub enum TriggerEventType {
/// Stub: no trigger type defined yet.
Undefined,
// Future variants (examples only — NOT implemented):
// WeatherEvent { event_code: u32, threshold_value: i128 },
// FlightCancellation { flight_id: String },
// PriceDeviation { asset: String, deviation_bps: u32 },
// Custom { namespace: String, predicate: Vec<u8> },
}
/// On-chain oracle trigger record.
///
/// This struct represents a signed attestation from an oracle source
/// indicating that a trigger condition has been met for a policy.
///
/// SECURITY INVARIANT (enforced by design):
/// In default (non-experimental) builds, no code path exists to accept
/// or process these records. Experimental builds MUST complete crypto
/// review before any signature verification logic is activated.
///
/// DATA INTEGRITY NOTE:
/// The `signature` field is RESERVED for future cryptographic verification.
/// Currently it MUST be empty. Parsing untrusted signatures without a
/// complete crypto design review is FORBIDDEN.
#[cfg(feature = "experimental")]
#[contracttype]
#[derive(Clone)]
pub struct OracleTrigger {
/// Policy this trigger applies to.
pub policy_id: u32,
/// Type of trigger event.
pub event_type: TriggerEventType,
/// Oracle source that attested this event.
pub source: OracleSource,
/// Event-specific payload (schema depends on event_type).
/// Must be validated against event_type schema before use.
pub payload: Bytes,
/// Unix timestamp when the oracle attested this event.
pub timestamp: u64,
/// Ledger sequence when this trigger was recorded.
pub trigger_ledger: u32,
/// Reserved for future Ed25519/EdDSA signature verification.
///
/// CRITICAL SECURITY NOTE:
/// This field MUST be empty in all current builds. Signature
/// verification is NOT implemented. Any non-empty signature
/// should be treated as INVALID until crypto review completes.
///
/// DO NOT PARSE: This field may contain arbitrary data that could
/// trigger parsing vulnerabilities if interpreted without validation.
pub signature: Bytes,
}
#[cfg(not(feature = "experimental"))]
#[contracttype]
#[derive(Clone)]
pub struct OracleTrigger {
pub policy_id: u32,
pub event_type: TriggerEventType,
pub source: OracleSource,
pub payload: Bytes,
pub timestamp: u64,
pub trigger_ledger: u32,
pub signature: Bytes,
}
/// Status of an oracle trigger in the resolution pipeline.
#[cfg(feature = "experimental")]
#[contracttype]
#[derive(Clone, PartialEq, Debug)]
pub enum TriggerStatus {
/// Trigger recorded but not yet validated.
Pending,
/// Trigger passed all validation checks.
Validated,
/// Trigger rejected (invalid signature, replayed, etc.).
Rejected,
/// Trigger executed (payout initiated).
Executed,
/// Trigger expired (TTL exceeded).
Expired,
}
#[cfg(not(feature = "experimental"))]
#[contracttype]
#[derive(Clone, PartialEq, Debug)]
pub enum TriggerStatus {
Pending,
Validated,
Rejected,
Executed,
Expired,
}
/// Stub struct representing a resolved oracle-based claim.
///
/// This is a placeholder for the future parametric claim flow where
/// oracle attestations auto-generate claims without manual filing.
///
/// CLAIM GENERATION NOTE:
/// Automatic claim generation via oracle triggers requires:
/// 1. Cryptographic signature verification (TBD algorithm)
/// 2. Replay protection (nonce + TTL validation)
/// 3. Threshold quorum for multi-oracle sources
/// 4. Legal classification of auto-triggered payouts
#[cfg(feature = "experimental")]
#[contracttype]
#[derive(Clone)]
pub struct ParametricClaim {
/// Original claim_id from the standard claims system.
pub claim_id: u64,
/// Trigger that caused this claim.
pub trigger_id: u64,
/// Amount determined by the parametric schedule.
pub amount: i128,
/// Status of the parametric resolution.
pub status: TriggerStatus,
/// Block height when resolution occurred.
pub resolved_ledger: u32,
}
#[cfg(not(feature = "experimental"))]
#[contracttype]
#[derive(Clone)]
pub struct ParametricClaim {
pub claim_id: u64,
pub trigger_id: u64,
pub amount: i128,
pub status: TriggerStatus,
pub resolved_ledger: u32,
}