From a96172c095679a1e3cad43b1075126eaf212dfe5 Mon Sep 17 00:00:00 2001 From: cashmotor01 Date: Mon, 31 Aug 2026 09:24:07 +0000 Subject: [PATCH] fix(contracts): add DivisionByZero error variant for checked_div sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distinguish divide-by-zero from genuine arithmetic overflow. Previously all checked_div calls mapped None to ContractError::Overflow, making it impossible to tell from the error code alone whether the failure was a real i128 overflow or a degenerate zero divisor (e.g. broken adapter reporting zero total_assets). Changes: - vault: add DivisionByZero = 19 to ContractError; update 4 checked_div sites (deposit shares, withdraw adapter shares, principal_out, migrate_adapter slippage bound) to use it - blend-adapter: add DivisionByZero = 3 to its own ContractError; update checked_div(RATE_SCALAR) in b_tokens_to_usdc to use it Overflow is now reserved exclusively for checked_mul/checked_add failures. No breaking change — new variants are appended. --- packages/contracts/blend-adapter/src/lib.rs | 7 ++++++- packages/contracts/vault/src/lib.rs | 12 ++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/contracts/blend-adapter/src/lib.rs b/packages/contracts/blend-adapter/src/lib.rs index 483bbd45..7007f8e3 100644 --- a/packages/contracts/blend-adapter/src/lib.rs +++ b/packages/contracts/blend-adapter/src/lib.rs @@ -42,7 +42,7 @@ fn b_tokens_to_usdc(b_tokens: i128, b_rate: i128) -> Result .checked_mul(b_rate) .ok_or(ContractError::Overflow)? .checked_div(RATE_SCALAR) - .ok_or(ContractError::Overflow) + .ok_or(ContractError::DivisionByZero) } // --------------------------------------------------------------------------- @@ -133,6 +133,11 @@ pub enum ContractError { AlreadyInitialized = 1, /// An intermediate arithmetic operation would overflow `i128`. Overflow = 2, + /// A `checked_div` returned `None` because the divisor was zero. + /// Distinct from `Overflow`: this points to a degenerate adapter state + /// (e.g. a zero `b_rate` from a broken Blend pool) rather than a genuine + /// arithmetic overflow. + DivisionByZero = 3, } impl From for ContractError { diff --git a/packages/contracts/vault/src/lib.rs b/packages/contracts/vault/src/lib.rs index 5da2079f..9567e868 100644 --- a/packages/contracts/vault/src/lib.rs +++ b/packages/contracts/vault/src/lib.rs @@ -113,6 +113,10 @@ pub enum ContractError { NoAdapterPosition = 13, /// `migrate_adapter` was called with `max_slippage_bps > 10_000`. InvalidSlippageBps = 14, + /// A `checked_div` returned `None` because the divisor was zero. + /// Distinct from `Overflow`: this points to a degenerate adapter state + /// (e.g. zero `total_assets`) rather than a genuine arithmetic overflow. + DivisionByZero = 19, } // --------------------------------------------------------------------------- @@ -183,7 +187,7 @@ impl MeridianVault { .checked_mul(total_shares + OFFSET) .ok_or(ContractError::Overflow)? .checked_div(total_assets + OFFSET) - .ok_or(ContractError::Overflow)?; + .ok_or(ContractError::DivisionByZero)?; if shares_to_mint <= 0 { return Err(ContractError::DepositTooSmall); @@ -277,7 +281,7 @@ impl MeridianVault { .checked_mul(total_adapter_shares) .ok_or(ContractError::Overflow)? .checked_div(total_shares) - .ok_or(ContractError::Overflow)?; + .ok_or(ContractError::DivisionByZero)?; // Adapter redeems protocol shares, delivers USDC to vault, returns amount. let usdc_out = AdapterClient::new(&env, &adapter_addr) @@ -309,7 +313,7 @@ impl MeridianVault { .checked_mul(shares) .ok_or(ContractError::Overflow)? .checked_div(caller_shares) - .ok_or(ContractError::Overflow)?; + .ok_or(ContractError::DivisionByZero)?; env.storage() .persistent() .set(&principal_key, &(principal - principal_out)); @@ -508,7 +512,7 @@ impl MeridianVault { .checked_mul(10_000i128 - max_slippage_bps as i128) .ok_or(ContractError::Overflow)? .checked_div(10_000i128) - .ok_or(ContractError::Overflow)?; + .ok_or(ContractError::DivisionByZero)?; if value_after < min_acceptable { return Err(ContractError::MigrationValueDrift); }