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
5 changes: 5 additions & 0 deletions contracts/oracle-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,11 @@ impl OracleVerifier {
/// consume storage/instruction budget (griefing).
pub fn set_min_submit_interval(env: Env, admin: Address, seconds: u64) {
Self::require_admin(&env, &admin);
// Issue #624: reject zero -- a zero interval removes the per-oracle
// rate-limit guard and allows unlimited oracle submissions per block.
if seconds == 0 {
panic_with_error!(&env, Error::InvalidInput);
}
env.storage()
.instance()
.set(&StorageKey::MinSubmitInterval, &seconds);
Expand Down
14 changes: 14 additions & 0 deletions contracts/policy-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,11 @@ impl PolicyEngine {
if duration_days == 0 {
panic_with_error!(env, Error::InvalidDurationRange);
}
// Issue #623: validate duration_days against the product ceiling
// explicitly. The computed (end_time - start_time) must never
// exceed max_duration_days * 86_400 seconds. Checking the
// parameter here (before any arithmetic) ensures this holds even
// for scheduled-start policies where start_time != now.
if duration_days > product.max_duration_days {
panic_with_error!(env, Error::DurationTooLong);
}
Expand Down Expand Up @@ -683,6 +688,15 @@ impl PolicyEngine {
if end_time <= start_time {
panic_with_error!(env, Error::InvalidDurationRange);
}
// Issue #623: defense-in-depth -- confirm the computed span does
// not exceed max_duration_days * 86_400 seconds. This catches any
// future code path that might compute duration_secs independently.
let max_duration_secs = (product.max_duration_days as u64)
.checked_mul(86_400)
.unwrap_or(u64::MAX);
if end_time.saturating_sub(start_time) > max_duration_secs {
panic_with_error!(env, Error::DurationTooLong);
}
let policy_id = Self::next_policy_id(env);

let policy = Policy {
Expand Down
12 changes: 12 additions & 0 deletions contracts/risk-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ impl RiskPool {
}
minted - burn_shares
} else {
// Issue #621: integer division truncates toward zero; the
// MIN_SHARES guard below catches the resulting 0 case.
amount.checked_mul(total_shares)
.and_then(|v| v.checked_div(total_deposited))
.unwrap_or_else(|| panic_with_error!(&env, Error::Overflow))
Expand Down Expand Up @@ -539,6 +541,11 @@ impl RiskPool {

let available_liquidity = total_deposited.saturating_sub(total_locked);
if available_liquidity <= 0 { panic_with_error!(&env, Error::Undercollateralized); }
// Issue #621: shares * total_deposited / total_shares truncates
// toward zero; if the pool has absorbed losses total_deposited
// may be tiny relative to total_shares and a small redemption
// rounds to 0. The ZeroAmount guard below rejects such cases so
// no shares are ever burned without returning capital.
let amount = shares.checked_mul(total_deposited)
.and_then(|v| v.checked_div(total_shares))
.unwrap_or_else(|| panic_with_error!(&env, Error::Overflow));
Expand Down Expand Up @@ -692,9 +699,14 @@ impl RiskPool {
panic_with_error!(&env, Error::InsufficientShares);
}

// Issue #621: share-to-amount calculation can underflow to 0 when
// from_pos.deposited has been eroded by losses while shares stayed
// constant. A zero-amount transfer burns the shares without moving
// any capital, so reject it explicitly.
let amount = shares.checked_mul(from_pos.deposited)
.and_then(|v| v.checked_div(from_pos.shares))
.unwrap_or_else(|| panic_with_error!(&env, Error::Overflow));
if amount == 0 { panic_with_error!(&env, Error::ZeroAmount); }

let pending_yield_from = Self::settle_yield(&env, &mut from_pos);
from_pos.deposited = from_pos.deposited.saturating_sub(amount);
Expand Down