diff --git a/contracts/claims-processor/src/lib.rs b/contracts/claims-processor/src/lib.rs index df0deb6..739332a 100644 --- a/contracts/claims-processor/src/lib.rs +++ b/contracts/claims-processor/src/lib.rs @@ -2133,6 +2133,18 @@ impl ClaimsProcessor { Self::claim_payout(env, claim_id) } + /// Claim payout with explicit recipient verification (issue #718). + /// Rejects any attempt to redirect payout away from the claimant. + pub fn claim_payout_to(env: Env, claim_id: u128, recipient: Address) -> i128 { + let claim: Claim = env.storage().persistent() + .get(&StorageKey::Claim(claim_id)) + .unwrap_or_else(|| panic_with_error!(&env, Error::ClaimNotFound)); + if recipient != claim.claimant { + panic_with_error!(&env, Error::Unauthorized); + } + Self::claim_payout(env, claim_id) + } + /// Claim the payout for an approved claim after the payout delay has elapsed. /// /// When a payout delay is configured, approved claims enter Paid/PartiallyPaid diff --git a/contracts/governance-dao/src/lib.rs b/contracts/governance-dao/src/lib.rs index c5858f0..71b8906 100644 --- a/contracts/governance-dao/src/lib.rs +++ b/contracts/governance-dao/src/lib.rs @@ -1401,6 +1401,9 @@ impl GovernanceDao { if proposal.status == ProposalStatus::Cancelled { panic_with_error!(&env, Error::AlreadyCancelled); } + if proposal.status == ProposalStatus::Expired { + panic_with_error!(&env, Error::ExecutionDeadlineExpired); + } if proposal.status != ProposalStatus::Passed { panic_with_error!(&env, Error::ProposalNotPassed); } @@ -1460,6 +1463,11 @@ impl GovernanceDao { ); } + /// Execute proposal alias (issue #720), checking deadline and expiration. + pub fn execute_proposal(env: Env, proposal_id: u64) { + Self::execute(env, proposal_id); + } + /// Return the execution audit trail record for an executed proposal. pub fn get_execution_audit(env: Env, proposal_id: u64) -> ExecutionAuditRecord { env.storage() diff --git a/contracts/policy-engine/src/lib.rs b/contracts/policy-engine/src/lib.rs index bbdb973..17e8c14 100644 --- a/contracts/policy-engine/src/lib.rs +++ b/contracts/policy-engine/src/lib.rs @@ -991,6 +991,11 @@ impl PolicyEngine { .and_then(|v| v.checked_div(365)) .and_then(|v| v.checked_div(10_000)) .unwrap_or_else(|| panic_with_error!(env, Error::CoverageOutOfRange)); + + // SECURITY FIX (issue #719): Reject zero or negative premium amounts to prevent free coverage + if required_premium <= 0 { + panic_with_error!(env, Error::ZeroPremiumRate); + } // SECURITY FIX: reject payment if token does not match the product's expected token if payment_token != &product.expected_token { diff --git a/contracts/risk-pool/src/lib.rs b/contracts/risk-pool/src/lib.rs index 756fd91..f504cff 100644 --- a/contracts/risk-pool/src/lib.rs +++ b/contracts/risk-pool/src/lib.rs @@ -797,6 +797,14 @@ impl RiskPool { /// Transfer `shares` from `from` address to `to` address. /// Returns the proportional USDC deposit amount transferred. /// + /// Transfer LP shares between providers, validating amount is strictly positive (issue #717). + pub fn transfer_shares(env: Env, from: Address, to: Address, shares: i128) -> i128 { + if shares <= 0 { + panic_with_error!(&env, Error::ZeroAmount); + } + Self::transfer_position(env, from, to, shares) + } + /// The admin cannot be the receiving side either (issue #568) — /// otherwise `transfer_position` is a one-transaction way around the /// `deposit` restriction and the separation of duties it exists for.