Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions contracts/niffyinsure/src/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pub fn file_claim(
details: &String,
image_urls: &Vec<String>,
) -> Result<u64, Error> {
// Check pause: claims are blocked if claims_paused is true
storage::assert_claims_not_paused(env);

let policy = storage::get_policy(env, holder, policy_id).ok_or(Error::ClaimNotFound)?;

// Policy active window check using ledger helper.
Expand Down Expand Up @@ -96,6 +99,9 @@ pub fn vote_on_claim(
claim_id: u64,
vote: &VoteOption,
) -> Result<ClaimStatus, Error> {
// Check pause: voting is blocked if claims_paused is true
storage::assert_claims_not_paused(env);

let mut claim = storage::get_claim(env, claim_id).ok_or(Error::ClaimNotFound)?;

if claim.status.is_terminal() {
Expand Down Expand Up @@ -159,6 +165,9 @@ pub fn vote_on_claim(
/// Window check: `now >= filed_at + VOTE_WINDOW_LEDGERS` (via `ledger::is_vote_deadline_passed`).
/// Plurality wins; tie resolves to Rejected.
pub fn finalize_claim(env: &Env, claim_id: u64) -> Result<ClaimStatus, Error> {
// Check pause: finalization is blocked if claims_paused is true
storage::assert_claims_not_paused(env);

let mut claim = storage::get_claim(env, claim_id).ok_or(Error::ClaimNotFound)?;

if claim.status.is_terminal() {
Expand Down
77 changes: 71 additions & 6 deletions contracts/niffyinsure/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![no_std]

mod calculator;
mod claim;
mod ledger;
mod policy;
mod policy_lifecycle;
mod premium;
mod storage;
mod token;
Expand Down Expand Up @@ -176,26 +179,91 @@ impl NiffyInsure {
storage::get_active_policy_count(&env, &holder)
}

// ── Admin / pause ────────────────────────────────────────────────────
// ═════════════════════════════════════════════════════════════════════════════
// PAUSE SYSTEM
//
// Granular pause flags for operational flexibility:
// - bind_paused: blocks new policy initiation/renewal
// - claims_paused: blocks filing claims and voting
//
// Admin-only toggles with optional reason codes.
// Read-only methods continue to work for transparency.
// ═════════════════════════════════════════════════════════════════════════════

pub fn pause(env: Env, admin: Address) {
/// Pause the contract with optional reason code.
/// Reason codes: 0=maintenance, 1=vulnerability, 2=key_compromise, 3=other
/// Emits PauseToggled event with admin, paused=true, and reason code.
pub fn pause(env: Env, admin: Address, reason_code: u32) {
admin.require_auth();
let stored_admin = storage::get_admin(&env);
assert!(admin == stored_admin, "only admin can pause");
storage::set_paused(&env, true);

// Emit PauseToggled event for monitoring
env.events().publish(
(symbol_short!("p_toggle"),),
(admin, true, reason_code),
);
}

pub fn unpause(env: Env, admin: Address) {
/// Unpause the contract with optional reason code.
/// Reason codes: 0=resolved, 1=manual, 2=other
/// Emits PauseToggled event with admin, paused=false, and reason code.
pub fn unpause(env: Env, admin: Address, reason_code: u32) {
admin.require_auth();
let stored_admin = storage::get_admin(&env);
assert!(admin == stored_admin, "only admin can unpause");
storage::set_paused(&env, false);

// Emit PauseToggled event for monitoring
env.events().publish(
(symbol_short!("p_toggle"),),
(admin, false, reason_code),
);
}

/// Granular pause: pause only policy binding (initiate/renew).
pub fn pause_bind(env: Env, admin: Address, reason_code: u32) {
admin.require_auth();
let stored_admin = storage::get_admin(&env);
assert!(admin == stored_admin, "only admin can pause");

let mut flags = storage::get_pause_flags(&env);
flags.bind_paused = true;
storage::set_pause_flags(&env, &flags);

env.events().publish(
(symbol_short!("p_toggle"),),
(admin, true, reason_code),
);
}

/// Granular pause: pause only claims (file/vote/finalize).
pub fn pause_claims(env: Env, admin: Address, reason_code: u32) {
admin.require_auth();
let stored_admin = storage::get_admin(&env);
assert!(admin == stored_admin, "only admin can pause");

let mut flags = storage::get_pause_flags(&env);
flags.claims_paused = true;
storage::set_pause_flags(&env, &flags);

env.events().publish(
(symbol_short!("p_toggle"),),
(admin, true, reason_code),
);
}

/// Get current pause state (legacy - true if ANY pause flag is set).
pub fn is_paused(env: Env) -> bool {
storage::is_paused(&env)
}

/// Get detailed pause flags (bind_paused, claims_paused).
pub fn get_pause_flags(env: Env) -> storage::PauseFlags {
storage::get_pause_flags(&env)
}

// ── Test-only helpers ─────────────────────────────────────────────────

#[cfg(feature = "testutils")]
Expand Down Expand Up @@ -231,6 +299,3 @@ impl NiffyInsure {
storage::remove_voter(&env, &holder);
}
}

// Re-export error type so tests can reference it without the module path.
pub use claim::ContractError;
5 changes: 2 additions & 3 deletions contracts/niffyinsure/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,8 @@ pub fn initiate_policy(
base_amount: i128,
asset: Address,
) -> Result<Policy, PolicyError> {
if storage::is_paused(env) {
return Err(PolicyError::ContractPaused);
}
// Check granular pause: policy binding should be blocked if bind_paused
storage::assert_bind_not_paused(env);

// Asset allowlist check — before auth so callers get a clear error.
if !storage::is_allowed_asset(env, &asset) {
Expand Down
78 changes: 74 additions & 4 deletions contracts/niffyinsure/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,87 @@ pub fn is_allowed_asset(env: &Env, asset: &Address) -> bool {
.unwrap_or(false)
}

// ── Pause flag ────────────────────────────────────────────────────────────────
// ═════════════════════════════════════════════════════════════════════════════
// PAUSE SYSTEM
//
// Granular pause flags for operational flexibility:
// - bind_paused: blocks new policy initiation/renewal
// - claims_paused: blocks filing claims and voting
//
// Read-only methods continue to work for transparency.
// Admin-triggered payouts (process_claim) continue during pause to avoid trapping funds.
// ═════════════════════════════════════════════════════════════════════════════

/// Pause flags: separate controls for binding new policies vs filing claims.
/// Both false by default (unpaused state).
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PauseFlags {
pub bind_paused: bool,
pub claims_paused: bool,
}

pub fn set_paused(env: &Env, paused: bool) {
env.storage().instance().set(&DataKey::Paused, &paused);
impl Default for PauseFlags {
fn default() -> Self {
Self {
bind_paused: false,
claims_paused: false,
}
}
}

/// Central assertion: panics if ANY pause flag is set.
/// Use for entrypoints that should be blocked by any pause.
pub fn assert_not_paused(env: &Env) {
if is_paused(env) {
panic!("protocol paused for maintenance");
}
}

/// Assertion for policy binding operations (initiate/renew policy).
/// Only blocks if bind_paused is true.
pub fn assert_bind_not_paused(env: &Env) {
let flags = get_pause_flags(env);
if flags.bind_paused {
panic!("protocol paused for maintenance: policy binding disabled");
}
}

/// Assertion for claim operations (file claim, vote, finalize).
/// Only blocks if claims_paused is true.
pub fn assert_claims_not_paused(env: &Env) {
let flags = get_pause_flags(env);
if flags.claims_paused {
panic!("protocol paused for maintenance: claims disabled");
}
}

/// Get current pause state (legacy compatibility - returns true if ANY flag is set).
pub fn is_paused(env: &Env) -> bool {
let flags = get_pause_flags(env);
flags.bind_paused || flags.claims_paused
}

/// Get detailed pause flags.
pub fn get_pause_flags(env: &Env) -> PauseFlags {
env.storage()
.instance()
.get(&DataKey::Paused)
.unwrap_or(false)
.unwrap_or_default()
}

/// Set full pause state (legacy compatibility - sets both flags).
pub fn set_paused(env: &Env, paused: bool) {
let flags = PauseFlags {
bind_paused: paused,
claims_paused: paused,
};
env.storage().instance().set(&DataKey::Paused, &flags);
}

/// Set granular pause flags.
pub fn set_pause_flags(env: &Env, flags: &PauseFlags) {
env.storage().instance().set(&DataKey::Paused, flags);
}

// ── Claim counter (instance) ──────────────────────────────────────────────────
Expand Down
17 changes: 17 additions & 0 deletions contracts/niffyinsure/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ pub enum VoteOption {
Reject,
}

/// Reason for policy termination.
#[contracttype]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TerminationReason {
None,
VoluntaryCancellation,
LapsedNonPayment,
UnderwritingVoid,
FraudOrMisrepresentation,
RegulatoryAction,
AdminOverride,
}

// ── Premium engine structs ────────────────────────────────────────────────────

#[contracttype]
Expand Down Expand Up @@ -136,6 +149,10 @@ pub struct Policy {
/// 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,
}

/// On-chain claim record.
Expand Down
Loading