Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Error Handling

Evolve uses error codes for compact, deterministic error handling.

Defining Errors

Use the define_error! macro:

use evolve_core::define_error;
 
define_error!(ERR_NOT_ENOUGH_BALANCE, 0x01, "not enough balance");
define_error!(ERR_UNAUTHORIZED, 0x02, "unauthorized");
define_error!(ERR_OVERFLOW, 0x03, "arithmetic overflow");

Error Code Namespacing

Prevent collisions between modules with namespaced codes:

// Each module gets a range
const MODULE_BASE: u16 = 0x0100;
 
define_error!(ERR_MY_ERROR, MODULE_BASE | 0x01, "my error");
define_error!(ERR_ANOTHER, MODULE_BASE | 0x02, "another error");

Reserved Ranges

RangeModule
0x0000-0x00FFCore SDK errors
0x0100-0x01FFToken module
0x0200-0x02FFGas module
0x0300-0x03FFScheduler module
0x0400+Custom modules

Core SDK Errors

ErrorCodeDescription
ERR_ENCODING0x01Borsh encoding/decoding failed
ERR_UNKNOWN_FUNCTION0x02Function ID not found
ERR_ACCOUNT_NOT_INIT0x03Account not initialized
ERR_UNAUTHORIZED0x04Sender not authorized
ERR_NOT_PAYABLE0x05Function doesn't accept funds
ERR_ONE_COIN0x06Expected exactly one coin
ERR_INCOMPATIBLE_FA0x07Incompatible fungible asset
ERR_INSUFFICIENT_BALANCE0x08Insufficient balance
ERR_OVERFLOW0x09Arithmetic overflow

Using ensure!

For conditional error returns:

use evolve_core::ensure;
 
fn transfer(&self, to: AccountId, amount: u128, env: &mut dyn Environment) -> SdkResult<()> {
    let balance = self.balances.get(&env.sender(), env)?;
 
    // Returns Err(ERR_INSUFFICIENT_BALANCE) if false
    ensure!(balance >= amount, ERR_INSUFFICIENT_BALANCE);
 
    // ... proceed with transfer
    Ok(())
}

Error Propagation

Use ? for clean propagation:

fn complex_operation(&self, env: &mut dyn Environment) -> SdkResult<()> {
    let value = self.storage.get(env)?;
    let result = self.other_account.query(env)?;
    self.storage.set(&new_value, env)?;
    Ok(())
}

Handling Optional Values

// BAD - panics if not found
let value = self.data.may_get(env)?.unwrap();
 
// GOOD - returns error if not found
let value = self.data.may_get(env)?.ok_or(ERR_NOT_FOUND)?;
 
// GOOD - use default
let value = self.data.may_get(env)?.unwrap_or_default();

Transaction Atomicity

Errors automatically trigger rollback:

fn multi_step_operation(&self, env: &mut dyn Environment) -> SdkResult<()> {
    self.step_one(env)?;  // Succeeds
    self.step_two(env)?;  // Fails - step_one is rolled back
    Ok(())
}

Error Decoding (Development)

Enable human-readable errors:

[dependencies]
evolve_core = { workspace = true, features = ["error-decode"] }

Best Practices

  1. Use namespaced error codes - Prevent collisions
  2. Prefer ensure! - Cleaner than manual if/return
  3. Never use unwrap() - Use ok_or(ERR_...) or unwrap_or_default()
  4. Document error conditions - Add doc comments
  5. Keep error messages short - They're stored in the binary