diff --git a/contracts/claims-processor/src/lib.rs b/contracts/claims-processor/src/lib.rs index 2aab984..3984e5b 100644 --- a/contracts/claims-processor/src/lib.rs +++ b/contracts/claims-processor/src/lib.rs @@ -1367,6 +1367,24 @@ impl ClaimsProcessor { ); } + /// Explicitly reject a claim and emit a claim_rejected event with reason code (#695). + pub fn reject_claim(env: Env, admin: Address, claim_id: u128, reason: Symbol) { + Self::require_admin(&env, &admin); + let mut claim: Claim = env.storage().persistent() + .get(&StorageKey::Claim(claim_id)) + .unwrap_or_else(|| panic_with_error!(&env, Error::ClaimNotFound)); + claim.status = ClaimStatus::Rejected; + let claim_key = StorageKey::Claim(claim_id); + env.storage().persistent().set(&claim_key, &claim); + Self::extend_claim_ttl(&env, &claim_key); + Self::remove_from_pending(&env, claim_id); + + env.events().publish( + (Symbol::new(&env, "claim_rejected"),), + (claim_id, claim.policy_id, reason), + ); + } + // ── Queries ─────────────────────────────────────────────────────────────── /// Return the `Claim` record for the given `claim_id`. Panics with `ClaimNotFound` if it does not exist. diff --git a/contracts/governance-dao/src/lib.rs b/contracts/governance-dao/src/lib.rs index 935f2a8..e7770f0 100644 --- a/contracts/governance-dao/src/lib.rs +++ b/contracts/governance-dao/src/lib.rs @@ -1952,6 +1952,11 @@ impl GovernanceDao { panic_with_error!(&env, Error::EmptyDescription); } + // Validate template payload structure (#694) + if min_title_len == 0 || min_title_len > 256 || required_arg_count > 64 { + panic_with_error!(&env, Error::InvalidParameter); + } + if env.storage().instance().has(&StorageKey::Template(name.clone())) { panic_with_error!(&env, Error::DuplicateTemplateName); } diff --git a/contracts/policy-engine/src/lib.rs b/contracts/policy-engine/src/lib.rs index 171b981..40b1bb2 100644 --- a/contracts/policy-engine/src/lib.rs +++ b/contracts/policy-engine/src/lib.rs @@ -1607,6 +1607,13 @@ impl PolicyEngine { .set(&StorageKey::PendingAdminSince, &env.ledger().timestamp()); } + /// Cancel a pending admin transfer during the timelock window (#692). + pub fn cancel_admin_transfer(env: Env, admin: Address) { + Self::require_admin(&env, &admin); + env.storage().instance().remove(&StorageKey::PendingAdmin); + env.storage().instance().remove(&StorageKey::PendingAdminSince); + } + /// Accept the proposed admin. Only the proposed admin can call this, and /// only once `ADMIN_TRANSFER_TIMELOCK` has elapsed since the transfer was /// proposed (issue #356). diff --git a/contracts/risk-pool/src/lib.rs b/contracts/risk-pool/src/lib.rs index 6447eea..0e217c2 100644 --- a/contracts/risk-pool/src/lib.rs +++ b/contracts/risk-pool/src/lib.rs @@ -1725,13 +1725,16 @@ impl RiskPool { env.storage().persistent().get(&StorageKey::LpPosition(provider)) } - /// Return the pool utilisation rate in basis points (locked / deposited × 10,000). + /// Return the pool utilisation rate in basis points ((locked + queued_exits) / deposited × 10,000). + /// Includes committed exit queue shares in utilization calculation (#693). /// Returns 0 if no USDC has been deposited. pub fn get_utilization_rate(env: Env) -> u32 { let deposited: i128 = env.storage().instance().get(&StorageKey::TotalDeposited).unwrap_or(0); let locked: i128 = env.storage().instance().get(&StorageKey::TotalLocked).unwrap_or(0); + let queued_exits: i128 = env.storage().instance().get(&StorageKey::QueuedExitShares).unwrap_or(0); if deposited == 0 { return 0; } - let util_bps = locked.checked_mul(10_000) + let effective_locked = locked.saturating_add(queued_exits); + let util_bps = effective_locked.checked_mul(10_000) .and_then(|v| v.checked_div(deposited)) .unwrap_or(0); // Saturate to u32::MAX if the result exceeds u32 range