Skip to content

Commit a034fde

Browse files
committed
feat: implement granular pause system with reason codes and events
1 parent 119fa1f commit a034fde

6 files changed

Lines changed: 173 additions & 40 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/niffyinsure/src/claim.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub fn file_claim(
3434
details: &String,
3535
image_urls: &Vec<String>,
3636
) -> Result<u64, Error> {
37+
// Check pause: claims are blocked if claims_paused is true
38+
storage::assert_claims_not_paused(env);
39+
3740
let policy = storage::get_policy(env, holder, policy_id).ok_or(Error::ClaimNotFound)?;
3841

3942
// Policy active window check using ledger helper.
@@ -96,6 +99,9 @@ pub fn vote_on_claim(
9699
claim_id: u64,
97100
vote: &VoteOption,
98101
) -> Result<ClaimStatus, Error> {
102+
// Check pause: voting is blocked if claims_paused is true
103+
storage::assert_claims_not_paused(env);
104+
99105
let mut claim = storage::get_claim(env, claim_id).ok_or(Error::ClaimNotFound)?;
100106

101107
if claim.status.is_terminal() {
@@ -159,6 +165,9 @@ pub fn vote_on_claim(
159165
/// Window check: `now >= filed_at + VOTE_WINDOW_LEDGERS` (via `ledger::is_vote_deadline_passed`).
160166
/// Plurality wins; tie resolves to Rejected.
161167
pub fn finalize_claim(env: &Env, claim_id: u64) -> Result<ClaimStatus, Error> {
168+
// Check pause: finalization is blocked if claims_paused is true
169+
storage::assert_claims_not_paused(env);
170+
162171
let mut claim = storage::get_claim(env, claim_id).ok_or(Error::ClaimNotFound)?;
163172

164173
if claim.status.is_terminal() {

contracts/niffyinsure/src/lib.rs

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#![no_std]
22

3+
mod calculator;
34
mod claim;
5+
mod ledger;
46
mod policy;
7+
mod policy_lifecycle;
58
mod premium;
69
mod storage;
710
mod token;
@@ -176,26 +179,91 @@ impl NiffyInsure {
176179
storage::get_active_policy_count(&env, &holder)
177180
}
178181

179-
// ── Admin / pause ────────────────────────────────────────────────────
182+
// ═════════════════════════════════════════════════════════════════════════════
183+
// PAUSE SYSTEM
184+
//
185+
// Granular pause flags for operational flexibility:
186+
// - bind_paused: blocks new policy initiation/renewal
187+
// - claims_paused: blocks filing claims and voting
188+
//
189+
// Admin-only toggles with optional reason codes.
190+
// Read-only methods continue to work for transparency.
191+
// ═════════════════════════════════════════════════════════════════════════════
180192

181-
pub fn pause(env: Env, admin: Address) {
193+
/// Pause the contract with optional reason code.
194+
/// Reason codes: 0=maintenance, 1=vulnerability, 2=key_compromise, 3=other
195+
/// Emits PauseToggled event with admin, paused=true, and reason code.
196+
pub fn pause(env: Env, admin: Address, reason_code: u32) {
182197
admin.require_auth();
183198
let stored_admin = storage::get_admin(&env);
184199
assert!(admin == stored_admin, "only admin can pause");
185200
storage::set_paused(&env, true);
201+
202+
// Emit PauseToggled event for monitoring
203+
env.events().publish(
204+
(symbol_short!("p_toggle"),),
205+
(admin, true, reason_code),
206+
);
186207
}
187208

188-
pub fn unpause(env: Env, admin: Address) {
209+
/// Unpause the contract with optional reason code.
210+
/// Reason codes: 0=resolved, 1=manual, 2=other
211+
/// Emits PauseToggled event with admin, paused=false, and reason code.
212+
pub fn unpause(env: Env, admin: Address, reason_code: u32) {
189213
admin.require_auth();
190214
let stored_admin = storage::get_admin(&env);
191215
assert!(admin == stored_admin, "only admin can unpause");
192216
storage::set_paused(&env, false);
217+
218+
// Emit PauseToggled event for monitoring
219+
env.events().publish(
220+
(symbol_short!("p_toggle"),),
221+
(admin, false, reason_code),
222+
);
223+
}
224+
225+
/// Granular pause: pause only policy binding (initiate/renew).
226+
pub fn pause_bind(env: Env, admin: Address, reason_code: u32) {
227+
admin.require_auth();
228+
let stored_admin = storage::get_admin(&env);
229+
assert!(admin == stored_admin, "only admin can pause");
230+
231+
let mut flags = storage::get_pause_flags(&env);
232+
flags.bind_paused = true;
233+
storage::set_pause_flags(&env, &flags);
234+
235+
env.events().publish(
236+
(symbol_short!("p_toggle"),),
237+
(admin, true, reason_code),
238+
);
193239
}
194240

241+
/// Granular pause: pause only claims (file/vote/finalize).
242+
pub fn pause_claims(env: Env, admin: Address, reason_code: u32) {
243+
admin.require_auth();
244+
let stored_admin = storage::get_admin(&env);
245+
assert!(admin == stored_admin, "only admin can pause");
246+
247+
let mut flags = storage::get_pause_flags(&env);
248+
flags.claims_paused = true;
249+
storage::set_pause_flags(&env, &flags);
250+
251+
env.events().publish(
252+
(symbol_short!("p_toggle"),),
253+
(admin, true, reason_code),
254+
);
255+
}
256+
257+
/// Get current pause state (legacy - true if ANY pause flag is set).
195258
pub fn is_paused(env: Env) -> bool {
196259
storage::is_paused(&env)
197260
}
198261

262+
/// Get detailed pause flags (bind_paused, claims_paused).
263+
pub fn get_pause_flags(env: Env) -> storage::PauseFlags {
264+
storage::get_pause_flags(&env)
265+
}
266+
199267
// ── Test-only helpers ─────────────────────────────────────────────────
200268

201269
#[cfg(feature = "testutils")]
@@ -231,6 +299,3 @@ impl NiffyInsure {
231299
storage::remove_voter(&env, &holder);
232300
}
233301
}
234-
235-
// Re-export error type so tests can reference it without the module path.
236-
pub use claim::ContractError;

contracts/niffyinsure/src/policy.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,8 @@ pub fn initiate_policy(
188188
base_amount: i128,
189189
asset: Address,
190190
) -> Result<Policy, PolicyError> {
191-
if storage::is_paused(env) {
192-
return Err(PolicyError::ContractPaused);
193-
}
191+
// Check granular pause: policy binding should be blocked if bind_paused
192+
storage::assert_bind_not_paused(env);
194193

195194
// Asset allowlist check — before auth so callers get a clear error.
196195
if !storage::is_allowed_asset(env, &asset) {

contracts/niffyinsure/src/storage.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,87 @@ pub fn is_allowed_asset(env: &Env, asset: &Address) -> bool {
148148
.unwrap_or(false)
149149
}
150150

151-
// ── Pause flag ────────────────────────────────────────────────────────────────
151+
// ═════════════════════════════════════════════════════════════════════════════
152+
// PAUSE SYSTEM
153+
//
154+
// Granular pause flags for operational flexibility:
155+
// - bind_paused: blocks new policy initiation/renewal
156+
// - claims_paused: blocks filing claims and voting
157+
//
158+
// Read-only methods continue to work for transparency.
159+
// Admin-triggered payouts (process_claim) continue during pause to avoid trapping funds.
160+
// ═════════════════════════════════════════════════════════════════════════════
161+
162+
/// Pause flags: separate controls for binding new policies vs filing claims.
163+
/// Both false by default (unpaused state).
164+
#[contracttype]
165+
#[derive(Clone, Debug, Eq, PartialEq)]
166+
pub struct PauseFlags {
167+
pub bind_paused: bool,
168+
pub claims_paused: bool,
169+
}
152170

153-
pub fn set_paused(env: &Env, paused: bool) {
154-
env.storage().instance().set(&DataKey::Paused, &paused);
171+
impl Default for PauseFlags {
172+
fn default() -> Self {
173+
Self {
174+
bind_paused: false,
175+
claims_paused: false,
176+
}
177+
}
178+
}
179+
180+
/// Central assertion: panics if ANY pause flag is set.
181+
/// Use for entrypoints that should be blocked by any pause.
182+
pub fn assert_not_paused(env: &Env) {
183+
if is_paused(env) {
184+
panic!("protocol paused for maintenance");
185+
}
186+
}
187+
188+
/// Assertion for policy binding operations (initiate/renew policy).
189+
/// Only blocks if bind_paused is true.
190+
pub fn assert_bind_not_paused(env: &Env) {
191+
let flags = get_pause_flags(env);
192+
if flags.bind_paused {
193+
panic!("protocol paused for maintenance: policy binding disabled");
194+
}
155195
}
156196

197+
/// Assertion for claim operations (file claim, vote, finalize).
198+
/// Only blocks if claims_paused is true.
199+
pub fn assert_claims_not_paused(env: &Env) {
200+
let flags = get_pause_flags(env);
201+
if flags.claims_paused {
202+
panic!("protocol paused for maintenance: claims disabled");
203+
}
204+
}
205+
206+
/// Get current pause state (legacy compatibility - returns true if ANY flag is set).
157207
pub fn is_paused(env: &Env) -> bool {
208+
let flags = get_pause_flags(env);
209+
flags.bind_paused || flags.claims_paused
210+
}
211+
212+
/// Get detailed pause flags.
213+
pub fn get_pause_flags(env: &Env) -> PauseFlags {
158214
env.storage()
159215
.instance()
160216
.get(&DataKey::Paused)
161-
.unwrap_or(false)
217+
.unwrap_or_default()
218+
}
219+
220+
/// Set full pause state (legacy compatibility - sets both flags).
221+
pub fn set_paused(env: &Env, paused: bool) {
222+
let flags = PauseFlags {
223+
bind_paused: paused,
224+
claims_paused: paused,
225+
};
226+
env.storage().instance().set(&DataKey::Paused, &flags);
227+
}
228+
229+
/// Set granular pause flags.
230+
pub fn set_pause_flags(env: &Env, flags: &PauseFlags) {
231+
env.storage().instance().set(&DataKey::Paused, flags);
162232
}
163233

164234
// ── Claim counter (instance) ──────────────────────────────────────────────────

contracts/niffyinsure/src/types.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ pub enum VoteOption {
8484
Reject,
8585
}
8686

87+
/// Reason for policy termination.
88+
#[contracttype]
89+
#[derive(Clone, PartialEq, Eq, Debug)]
90+
pub enum TerminationReason {
91+
None,
92+
VoluntaryCancellation,
93+
LapsedNonPayment,
94+
UnderwritingVoid,
95+
FraudOrMisrepresentation,
96+
RegulatoryAction,
97+
AdminOverride,
98+
}
99+
87100
// ── Premium engine structs ────────────────────────────────────────────────────
88101

89102
#[contracttype]
@@ -136,6 +149,10 @@ pub struct Policy {
136149
/// SEP-41 asset contract used for this policy's premium payment and claim payout.
137150
/// Must be allowlisted at the time of policy initiation.
138151
pub asset: Address,
152+
// Termination fields
153+
pub terminated_at_ledger: u32,
154+
pub termination_reason: TerminationReason,
155+
pub terminated_by_admin: bool,
139156
}
140157

141158
/// On-chain claim record.

0 commit comments

Comments
 (0)