Skip to content

bug(contracts): deposit() and execute_rebalance() use panic!() instead of typed errors — callers cannot handle errors gracefully #6

Description

@Uchechukwu-Ekezie

Description

The Soroban smart contract (contracts/src/lib.rs) uses panic!() for error conditions in deposit() and execute_rebalance(), while all other public functions consistently return Result<T, Error>. The inconsistency means on-chain callers (other contracts, SDK clients) cannot catch or recover from these panics — the transaction simply aborts with no structured error code.

Code Evidence

Inconsistent: deposit() uses panic!() — contracts/src/lib.rs

pub fn deposit(env: Env, portfolio_id: u64, amount: i64) {
    if amount <= 0 {
        panic!("Amount must be positive");  // ← Untyped panic
    }
    if let Some(true) = env.storage().instance().get(&DataKey::EmergencyStop) {
        panic!("Emergency stop active");    // ← Untyped panic
    }
    // ...
}

Inconsistent: execute_rebalance() uses panic!()

pub fn execute_rebalance(env: Env, portfolio_id: u64) {
    if let Some(true) = env.storage().instance().get(&DataKey::EmergencyStop) {
        panic!("Emergency stop active");    // ← Untyped panic
    }
    // ...
}

Correct pattern used elsewhere in the same file

pub fn initialize(env: Env, admin: Address, reflector_address: Address) -> Result<(), Error> {
    if env.storage().instance().has(&DataKey::Initialized) {
        return Err(Error::AlreadyInitialized);  // ← Typed error ✓
    }
    Ok(())
}

pub fn create_portfolio(...) -> Result<u64, Error> {
    if !portfolio::validate_allocations(&target_allocations) {
        return Err(Error::InvalidAllocation);  // ← Typed error ✓
    }
    // ...
}

Why This Matters

In Soroban, panic!() terminates the contract call immediately with no error code. This means:

  1. Calling contracts cannot distinguish errors — a calling contract using deposit() sees the same generic failure whether the amount was negative, the emergency stop was active, or an unrelated internal error occurred
  2. SDK clients cannot handle specific cases — frontend/backend code calling deposit() via Stellar SDK must show a generic error rather than "Emergency stop is active — please wait"
  3. Inconsistency creates maintenance bugs — future contributors might assume all functions return typed errors and write error-handling code that never triggers for deposit() and execute_rebalance()

Proposed Fix

1. Add missing error variants to contracts/src/types.rs

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
    // ... existing variants ...
    InvalidAmount    = 5,   // amount <= 0
    EmergencyStop    = 6,   // emergency stop is active
}

2. Update deposit() to return Result<(), Error>

pub fn deposit(env: Env, portfolio_id: u64, amount: i64) -> Result<(), Error> {
    if amount <= 0 {
        return Err(Error::InvalidAmount);
    }
    if let Some(true) = env.storage().instance().get(&DataKey::EmergencyStop) {
        return Err(Error::EmergencyStop);
    }
    // ... rest of function ...
    Ok(())
}

3. Update execute_rebalance() similarly

pub fn execute_rebalance(env: Env, portfolio_id: u64) -> Result<(), Error> {
    if let Some(true) = env.storage().instance().get(&DataKey::EmergencyStop) {
        return Err(Error::EmergencyStop);
    }
    // ...
    Ok(())
}

4. Update contracts/src/test.rs to assert specific error codes

#[test]
fn test_deposit_rejects_negative_amount() {
    // ...
    let result = client.try_deposit(&portfolio_id, &-100i64);
    assert_eq!(result, Err(Ok(Error::InvalidAmount)));
}

Files Affected

  • contracts/src/lib.rsdeposit() and execute_rebalance() (change signatures + replace panic!)
  • contracts/src/types.rs — add InvalidAmount and EmergencyStop error variants
  • contracts/src/test.rs — update tests to assert typed errors

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official Campaign

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions