soroban

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Soroban Smart Contracts

Soroban智能合约

End-to-end guide for building Soroban contracts: writing them, testing them, securing them, and shipping advanced architectures. This skill bundles five concerns that live and die together — the contract code, the tests, the security posture, the design patterns, and the gotchas.
构建Soroban合约的端到端指南:编写、测试、安全加固及部署高级架构。本指南整合了五个核心关注点——合约代码、测试用例、安全策略、设计模式以及常见陷阱。

When to use this skill

适用场景

  • Writing a Soroban contract in Rust
  • Setting up unit, integration, fuzz, or property tests
  • Reviewing a contract for security issues (authorization, reentrancy-adjacent bugs, storage hygiene, TTL, overflow)
  • Architecting upgradeable contracts, factories, governance, or DeFi primitives
  • Debugging a Soroban-specific error (auth, storage, archival, resource limits)
  • 使用Rust编写Soroban合约
  • 搭建单元测试、集成测试、模糊测试或属性测试
  • 审查合约安全问题(授权、重入相关漏洞、存储规范、TTL、溢出)
  • 设计可升级合约、工厂合约、治理机制或DeFi原语
  • 调试Soroban特定错误(授权、存储、归档、资源限制)

Related skills

相关技能

  • Assets, trustlines, and SAC bridge →
    ../assets/SKILL.md
  • Frontend/wallets that call your contract →
    ../dapp/SKILL.md
  • Chain data queries (RPC/Horizon) →
    ../data/SKILL.md
  • ZK cryptography (BLS12-381, BN254, Poseidon) →
    ../zk-proofs/SKILL.md
  • SEP/CAP standards and ecosystem links →
    ../standards/SKILL.md

  • 资产、信任线与SAC桥接 →
    ../assets/SKILL.md
  • 调用合约的前端/钱包 →
    ../dapp/SKILL.md
  • 链数据查询(RPC/Horizon) →
    ../data/SKILL.md
  • ZK密码学(BLS12-381、BN254、Poseidon) →
    ../zk-proofs/SKILL.md
  • SEP/CAP标准与生态链接 →
    ../standards/SKILL.md

Part 1: Contract Development

第一部分:合约开发

When to use Soroban

Soroban适用场景

Use Soroban when you need:
  • Custom on-chain logic beyond Stellar's built-in operations
  • Programmable escrow, lending, or DeFi primitives
  • Complex authorization rules
  • State management beyond account balances
  • Interoperability with Stellar Assets via SAC
当你需要以下功能时,选择Soroban:
  • 超出Stellar内置操作的自定义链上逻辑
  • 可编程托管、借贷或DeFi原语
  • 复杂授权规则
  • 账户余额之外的状态管理
  • 通过SAC与Stellar资产互操作

Quick Navigation

快速导航

Alternative Languages

替代语言

Rust is the primary and recommended language for Soroban contracts. Community-maintained alternatives exist but are not recommended for production:
  • AssemblyScript:
    as-soroban-sdk
    by Soneso — allows TypeScript-like syntax, officially listed on Stellar docs, but may lag behind the latest protocol version
  • Solidity: Hyperledger Solang — SDF-funded, compiles Solidity to Soroban WASM, currently pre-alpha (docs)
Rust是Soroban合约的首选推荐语言。社区维护的替代语言不建议用于生产环境:
  • AssemblyScript:Soneso开发的
    as-soroban-sdk
    —— 支持类TypeScript语法,已列入Stellar官方文档,但可能滞后于最新协议版本
  • SolidityHyperledger Solang —— 由SDF资助,可将Solidity编译为Soroban WASM,目前处于预alpha阶段文档

Architecture Overview

架构概述

Host-Guest Model

宿主-访客模型

Soroban uses a WebAssembly sandbox with strict separation:
  • Host Environment: Provides storage, crypto, cross-contract calls
  • Guest Contract: Your Rust code compiled to WASM
  • Contracts reference host objects via handles (not direct memory)
Soroban采用WebAssembly沙箱,严格分离:
  • 宿主环境:提供存储、加密、跨合约调用能力
  • 访客合约:编译为WASM的Rust代码
  • 合约通过句柄引用宿主对象(而非直接内存访问)

Key Constraints

关键约束

  • #![no_std]
    required - no Rust standard library
  • 64KB contract size limit (use release optimizations)
  • Limited heap allocation
  • No string type (use
    String
    from soroban-sdk or
    Symbol
    for short strings)
  • Symbol
    limited to 32 characters (was 10 in earlier versions)
  • 必须使用
    #![no_std]
    —— 不支持Rust标准库
  • 合约大小限制为64KB(使用release优化)
  • 堆分配受限
  • 无原生字符串类型(使用soroban-sdk的
    String
    或短字符串
    Symbol
  • Symbol
    最大长度为32字符(早期版本为10字符)

Project Setup

项目搭建

Initialize a new contract

初始化新合约

bash
stellar contract init my-contract
cd my-contract
This creates:
my-contract/
├── Cargo.toml
├── src/
│   └── lib.rs
└── contracts/
    └── hello_world/
        ├── Cargo.toml
        └── src/
            └── lib.rs
bash
stellar contract init my-contract
cd my-contract
生成的目录结构:
my-contract/
├── Cargo.toml
├── src/
│   └── lib.rs
└── contracts/
    └── hello_world/
        ├── Cargo.toml
        └── src/
            └── lib.rs

Cargo.toml configuration

Cargo.toml配置

toml
[package]
name = "my-contract"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "25.0.1"  # check https://crates.io/crates/soroban-sdk for latest

[dev-dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }  # match above

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true

[profile.release-with-logs]
inherits = "release"
debug-assertions = true
toml
[package]
name = "my-contract"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "25.0.1"  # 查看https://crates.io/crates/soroban-sdk获取最新版本

[dev-dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }  # 与上方版本保持一致

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true

[profile.release-with-logs]
inherits = "release"
debug-assertions = true

Contract Constructors (Protocol 22+)

合约构造函数(协议22+)

Use constructors for atomic initialization when protocol support is available. This avoids a separate
initialize
transaction and reduces front-running risk.
当协议支持时,使用构造函数进行原子初始化。这避免了单独的
initialize
交易,降低抢先交易风险。

Constructor pattern

构造函数模式

rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Value,
}

#[contract]
pub struct MyContract;

#[contractimpl]
impl MyContract {
    // Runs once at deployment time.
    pub fn __constructor(env: Env, admin: Address, initial_value: u32) {
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::Value, &initial_value);
    }
}
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Value,
}

#[contract]
pub struct MyContract;

#[contractimpl]
impl MyContract {
    // 部署时仅运行一次
    pub fn __constructor(env: Env, admin: Address, initial_value: u32) {
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::Value, &initial_value);
    }
}

Deploy with constructor args (CLI)

使用构造函数参数部署(CLI)

bash
stellar contract deploy \
  --wasm target/wasm32-unknown-unknown/release/my_contract.wasm \
  --source alice \
  --network testnet \
  -- \
  --admin alice \
  --initial_value 100
bash
stellar contract deploy \
  --wasm target/wasm32-unknown-unknown/release/my_contract.wasm \
  --source alice \
  --network testnet \
  -- \
  --admin alice \
  --initial_value 100

Rules

规则

  1. Name must be
    __constructor
    exactly.
  2. Constructor returns
    ()
    (no return value).
  3. Runs only at creation time and does not run on upgrade.
  4. If constructor fails, deployment fails atomically.
  1. 名称必须严格为
    __constructor
  2. 构造函数返回
    ()
    (无返回值)
  3. 仅在创建时运行,升级时不执行
  4. 若构造函数失败,部署会原子性失败

Backwards compatibility

向后兼容性

If targeting older protocol environments, use guarded
initialize
patterns and prevent re-initialization explicitly.
若针对旧版协议环境,使用受保护的
initialize
模式,并明确防止重复初始化。

Core Contract Structure

核心合约结构

Basic Contract

基础合约

rust
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec};

#[contract]
pub struct HelloContract;

#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
        vec![&env, symbol_short!("Hello"), to]
    }
}
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec};

#[contract]
pub struct HelloContract;

#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
        vec![&env, symbol_short!("Hello"), to]
    }
}

Contract with State

带状态的合约

rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Counter,
    Admin,
    UserBalance(Address),
}

#[contract]
pub struct CounterContract;

#[contractimpl]
impl CounterContract {
    pub fn initialize(env: Env, admin: Address) {
        if env.storage().instance().has(&DataKey::Admin) {
            panic!("already initialized");
        }
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::Counter, &0u32);
    }

    pub fn increment(env: Env) -> u32 {
        let mut count: u32 = env.storage().instance().get(&DataKey::Counter).unwrap_or(0);
        count += 1;
        env.storage().instance().set(&DataKey::Counter, &count);

        // Extend TTL to prevent archival
        env.storage().instance().extend_ttl(100, 518400); // threshold, ~30 days

        count
    }

    pub fn get_count(env: Env) -> u32 {
        env.storage().instance().get(&DataKey::Counter).unwrap_or(0)
    }
}
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Counter,
    Admin,
    UserBalance(Address),
}

#[contract]
pub struct CounterContract;

#[contractimpl]
impl CounterContract {
    pub fn initialize(env: Env, admin: Address) {
        if env.storage().instance().has(&DataKey::Admin) {
            panic!("already initialized");
        }
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::Counter, &0u32);
    }

    pub fn increment(env: Env) -> u32 {
        let mut count: u32 = env.storage().instance().get(&DataKey::Counter).unwrap_or(0);
        count += 1;
        env.storage().instance().set(&DataKey::Counter, &count);

        // 延长TTL防止归档
        env.storage().instance().extend_ttl(100, 518400); // 阈值,约30天

        count
    }

    pub fn get_count(env: Env) -> u32 {
        env.storage().instance().get(&DataKey::Counter).unwrap_or(0)
    }
}

Storage Types

存储类型

Soroban has three storage types with different costs and lifetimes:
Soroban提供三种存储类型,成本和生命周期各不相同:

Instance Storage

实例存储

  • Tied to contract instance lifetime
  • Shared across all users
  • Best for: admin addresses, global config, counters
rust
env.storage().instance().set(&key, &value);
env.storage().instance().get(&key);
env.storage().instance().extend_ttl(min_ttl, extend_to);
  • 与合约实例生命周期绑定
  • 所有用户共享
  • 适用场景:管理员地址、全局配置、计数器
rust
env.storage().instance().set(&key, &value);
env.storage().instance().get(&key);
env.storage().instance().extend_ttl(min_ttl, extend_to);

Persistent Storage

持久存储

  • Survives archival (can be restored)
  • Per-key TTL management
  • Best for: user balances, important state
rust
env.storage().persistent().set(&key, &value);
env.storage().persistent().get(&key);
env.storage().persistent().extend_ttl(&key, min_ttl, extend_to);
  • 可在归档后恢复
  • 支持按键管理TTL
  • 适用场景:用户余额、重要状态
rust
env.storage().persistent().set(&key, &value);
env.storage().persistent().get(&key);
env.storage().persistent().extend_ttl(&key, min_ttl, extend_to);

Temporary Storage

临时存储

  • Cheapest, automatically deleted when TTL expires
  • Cannot be restored after archival
  • Best for: caches, temporary flags, session data
rust
env.storage().temporary().set(&key, &value);
env.storage().temporary().get(&key);
env.storage().temporary().extend_ttl(&key, min_ttl, extend_to);
  • 成本最低,TTL到期后自动删除
  • 归档后无法恢复
  • 适用场景:缓存、临时标记、会话数据
rust
env.storage().temporary().set(&key, &value);
env.storage().temporary().get(&key);
env.storage().temporary().extend_ttl(&key, min_ttl, extend_to);

TTL Management

TTL管理

rust
// Check remaining TTL
let ttl = env.storage().persistent().get_ttl(&key);

// Extend if below threshold
const MIN_TTL: u32 = 17280;  // ~1 day at 5s ledgers
const EXTEND_TO: u32 = 518400;  // ~30 days

if ttl < MIN_TTL {
    env.storage().persistent().extend_ttl(&key, MIN_TTL, EXTEND_TO);
}
rust
// 检查剩余TTL
let ttl = env.storage().persistent().get_ttl(&key);

// 低于阈值时延长
const MIN_TTL: u32 = 17280;  // 约1天(账本间隔5秒)
const EXTEND_TO: u32 = 518400;  // 约30天

if ttl < MIN_TTL {
    env.storage().persistent().extend_ttl(&key, MIN_TTL, EXTEND_TO);
}

Data Types

数据类型

Primitive Types

基础类型

rust
use soroban_sdk::{Address, Bytes, BytesN, Map, String, Symbol, Vec, I128, U256};

// Address - account or contract identifier
let addr: Address = env.current_contract_address();

// Symbol - short strings (max 32 chars)
let sym: Symbol = symbol_short!("transfer");

// String - longer strings
let s: String = String::from_str(&env, "Hello, Stellar!");

// Fixed-size bytes
let hash: BytesN<32> = env.crypto().sha256(&bytes);

// Collections
let v: Vec<u32> = vec![&env, 1, 2, 3];
let m: Map<Symbol, u32> = Map::new(&env);
rust
use soroban_sdk::{Address, Bytes, BytesN, Map, String, Symbol, Vec, I128, U256};

// Address - 账户或合约标识符
let addr: Address = env.current_contract_address();

// Symbol - 短字符串(最大32字符)
let sym: Symbol = symbol_short!("transfer");

// String - 长字符串
let s: String = String::from_str(&env, "Hello, Stellar!");

// 固定长度字节
let hash: BytesN<32> = env.crypto().sha256(&bytes);

// 集合类型
let v: Vec<u32> = vec![&env, 1, 2, 3];
let m: Map<Symbol, u32> = Map::new(&env);

Custom Types

自定义类型

rust
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenMetadata {
    pub name: String,
    pub symbol: Symbol,
    pub decimals: u32,
}

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Balance(Address),
    Allowance(Address, Address),  // (owner, spender)
}
rust
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenMetadata {
    pub name: String,
    pub symbol: Symbol,
    pub decimals: u32,
}

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Balance(Address),
    Allowance(Address, Address),  // (所有者, 授权使用者)
}

Authorization

授权机制

Requiring Authorization

要求授权

rust
#[contractimpl]
impl TokenContract {
    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        // Require 'from' to authorize this call
        from.require_auth();

        // Or require auth for specific arguments
        from.require_auth_for_args((&to, amount).into_val(&env));

        // Transfer logic...
    }
}
rust
#[contractimpl]
impl TokenContract {
    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        // 要求`from`授权此调用
        from.require_auth();

        // 或针对特定参数要求授权
        from.require_auth_for_args((&to, amount).into_val(&env));

        // 转账逻辑...
    }
}

Admin Patterns

管理员模式

rust
fn require_admin(env: &Env) {
    let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
    admin.require_auth();
}

pub fn set_admin(env: Env, new_admin: Address) {
    require_admin(&env);
    env.storage().instance().set(&DataKey::Admin, &new_admin);
}
rust
fn require_admin(env: &Env) {
    let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
    admin.require_auth();
}

pub fn set_admin(env: Env, new_admin: Address) {
    require_admin(&env);
    env.storage().instance().set(&DataKey::Admin, &new_admin);
}

Cross-Contract Calls

跨合约调用

Calling Another Contract

调用其他合约

rust
use soroban_sdk::{contract, contractimpl, Address, Env};

mod token_contract {
    soroban_sdk::contractimport!(
        file = "../token/target/wasm32-unknown-unknown/release/token.wasm"
    );
}

#[contract]
pub struct VaultContract;

#[contractimpl]
impl VaultContract {
    pub fn deposit(env: Env, user: Address, token: Address, amount: i128) {
        user.require_auth();

        // Create client for token contract
        let token_client = token_contract::Client::new(&env, &token);

        // Call transfer on token contract
        token_client.transfer(&user, &env.current_contract_address(), &amount);

        // Update vault state...
    }
}
rust
use soroban_sdk::{contract, contractimpl, Address, Env};

mod token_contract {
    soroban_sdk::contractimport!(
        file = "../token/target/wasm32-unknown-unknown/release/token.wasm"
    );
}

#[contract]
pub struct VaultContract;

#[contractimpl]
impl VaultContract {
    pub fn deposit(env: Env, user: Address, token: Address, amount: i128) {
        user.require_auth();

        // 创建代币合约客户端
        let token_client = token_contract::Client::new(&env, &token);

        // 调用代币合约的transfer方法
        token_client.transfer(&user, &env.current_contract_address(), &amount);

        // 更新金库状态...
    }
}

Using Stellar Asset Contract (SAC)

使用Stellar资产合约(SAC)

rust
use soroban_sdk::token::Client as TokenClient;

pub fn transfer_asset(env: Env, from: Address, to: Address, asset: Address, amount: i128) {
    from.require_auth();

    let token = TokenClient::new(&env, &asset);
    token.transfer(&from, &to, &amount);
}
rust
use soroban_sdk::token::Client as TokenClient;

pub fn transfer_asset(env: Env, from: Address, to: Address, asset: Address, amount: i128) {
    from.require_auth();

    let token = TokenClient::new(&env, &asset);
    token.transfer(&from, &to, &amount);
}

Events

事件处理

Emitting Events

触发事件

rust
use soroban_sdk::{contract, contractevent, contractimpl, Address, Env};

#[contractevent(topics = ["transfer"])]
pub struct TransferEvent {
    pub from: Address,
    pub to: Address,
    pub amount: i128,
}

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        // ... transfer logic ...

        // Emit event
        TransferEvent { from, to, amount }.publish(&env);
    }
}
rust
use soroban_sdk::{contract, contractevent, contractimpl, Address, Env};

#[contractevent(topics = ["transfer"])]
pub struct TransferEvent {
    pub from: Address,
    pub to: Address,
    pub amount: i128,
}

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        // ... 转账逻辑 ...

        // 触发事件
        TransferEvent { from, to, amount }.publish(&env);
    }
}

Error Handling

错误处理

Custom Errors

自定义错误

rust
use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ContractError {
    AlreadyInitialized = 1,
    NotInitialized = 2,
    InsufficientBalance = 3,
    Unauthorized = 4,
    InvalidAmount = 5,
}

// Usage
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), ContractError> {
    if amount <= 0 {
        return Err(ContractError::InvalidAmount);
    }

    let balance: i128 = get_balance(&env, &from);
    if balance < amount {
        return Err(ContractError::InsufficientBalance);
    }

    // ... transfer logic ...
    Ok(())
}
rust
use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ContractError {
    AlreadyInitialized = 1,
    NotInitialized = 2,
    InsufficientBalance = 3,
    Unauthorized = 4,
    InvalidAmount = 5,
}

// 使用示例
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), ContractError> {
    if amount <= 0 {
        return Err(ContractError::InvalidAmount);
    }

    let balance: i128 = get_balance(&env, &from);
    if balance < amount {
        return Err(ContractError::InsufficientBalance);
    }

    // ... 转账逻辑 ...
    Ok(())
}

Building and Deploying

构建与部署

Build Contract

构建合约

bash
undefined
bash
undefined

Build optimized WASM

构建优化后的WASM

stellar contract build
stellar contract build

Output: target/wasm32-unknown-unknown/release/my_contract.wasm

输出路径: target/wasm32-unknown-unknown/release/my_contract.wasm

undefined
undefined

Deploy to Testnet

部署到测试网

bash
undefined
bash
undefined

Generate and fund a new identity

生成并资助新身份

stellar keys generate --global alice --network testnet --fund
stellar keys generate --global alice --network testnet --fund

Deploy contract

部署合约

stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/my_contract.wasm
--source alice
--network testnet
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/my_contract.wasm
--source alice
--network testnet

Returns: CONTRACT_ID (starts with 'C')

返回结果: CONTRACT_ID(以'C'开头)

undefined
undefined

Initialize Contract

初始化合约

bash
stellar contract invoke \
  --id CONTRACT_ID \
  --source alice \
  --network testnet \
  -- \
  initialize \
  --admin alice
bash
stellar contract invoke \
  --id CONTRACT_ID \
  --source alice \
  --network testnet \
  -- \
  initialize \
  --admin alice

Invoke Functions

调用合约函数

bash
stellar contract invoke \
  --id CONTRACT_ID \
  --source alice \
  --network testnet \
  -- \
  increment
bash
stellar contract invoke \
  --id CONTRACT_ID \
  --source alice \
  --network testnet \
  -- \
  increment

Unit Testing

单元测试

rust
#![cfg(test)]

use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::Env;

#[test]
fn test_increment() {
    let env = Env::default();
    let contract_id = env.register_contract(None, CounterContract);
    let client = CounterContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    client.initialize(&admin);

    assert_eq!(client.get_count(), 0);
    assert_eq!(client.increment(), 1);
    assert_eq!(client.increment(), 2);
    assert_eq!(client.get_count(), 2);
}

#[test]
fn test_transfer_with_auth() {
    let env = Env::default();
    env.mock_all_auths();  // Auto-approve all auth requests

    let contract_id = env.register_contract(None, TokenContract);
    let client = TokenContractClient::new(&env, &contract_id);

    let alice = Address::generate(&env);
    let bob = Address::generate(&env);

    // Mint tokens to alice
    client.mint(&alice, &1000);

    // Transfer from alice to bob
    client.transfer(&alice, &bob, &100);

    assert_eq!(client.balance(&alice), 900);
    assert_eq!(client.balance(&bob), 100);
}
rust
#![cfg(test)]

use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::Env;

#[test]
fn test_increment() {
    let env = Env::default();
    let contract_id = env.register_contract(None, CounterContract);
    let client = CounterContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    client.initialize(&admin);

    assert_eq!(client.get_count(), 0);
    assert_eq!(client.increment(), 1);
    assert_eq!(client.increment(), 2);
    assert_eq!(client.get_count(), 2);
}

#[test]
fn test_transfer_with_auth() {
    let env = Env::default();
    env.mock_all_auths();  // 自动批准所有授权请求

    let contract_id = env.register_contract(None, TokenContract);
    let client = TokenContractClient::new(&env, &contract_id);

    let alice = Address::generate(&env);
    let bob = Address::generate(&env);

    // 给alice铸币
    client.mint(&alice, &1000);

    // 从alice转账给bob
    client.transfer(&alice, &bob, &100);

    assert_eq!(client.balance(&alice), 900);
    assert_eq!(client.balance(&bob), 100);
}

Best Practices

最佳实践

Contract Size Optimization

合约大小优化

  • Use
    symbol_short!()
    for symbols under 9 chars (more efficient)
  • Avoid unnecessary string operations
  • Use appropriate storage type for data lifetime
  • Consider splitting large contracts
  • 对9字符以下的符号使用
    symbol_short!()
    (更高效)
  • 避免不必要的字符串操作
  • 根据数据生命周期选择合适的存储类型
  • 考虑拆分大型合约

Storage Efficiency

存储效率

  • Use compact data structures
  • Clean up temporary storage
  • Batch storage operations when possible
  • Manage TTLs proactively to avoid archival
  • 使用紧凑的数据结构
  • 清理临时存储
  • 尽可能批量处理存储操作
  • 主动管理TTL避免归档

Security

安全

  • Always validate inputs
  • Use
    require_auth()
    for sensitive operations
  • Check contract ownership in initialization
  • Prevent reinitialization attacks
  • Validate cross-contract call targets
  • 始终验证输入
  • 对敏感操作使用
    require_auth()
  • 初始化时检查合约所有权
  • 防止重复初始化攻击
  • 验证跨合约调用目标

Gas/Resource Optimization

Gas/资源优化

  • Minimize storage reads/writes
  • Use events for data that doesn't need on-chain queries
  • Batch operations where possible
  • Profile resource usage with
    stellar contract invoke --sim
  • 减少存储读写次数
  • 使用事件存储无需链上查询的数据
  • 尽可能批量操作
  • 使用
    stellar contract invoke --sim
    分析资源使用情况

Zero-Knowledge Cryptography (Status-Sensitive)

零知识密码学(状态敏感)

Stellar's ZK cryptography capabilities are evolving. Treat availability as protocol- and network-dependent.
  • CAP-0059: BLS12-381 primitives
  • CAP-0074: BN254 host functions (proposed)
  • CAP-0075: Poseidon/Poseidon2 host functions (proposed)
Before implementation, always verify:
  1. CAP status in the CAP preamble (
    Accepted
    /
    Implemented
    vs draft/awaiting decision)
  2. Target network software version and protocol support
  3. soroban-sdk
    release support for the target host functions
Stellar的ZK密码学功能正在演进。可用性取决于协议和网络支持。
  • CAP-0059: BLS12-381原语
  • CAP-0074: BN254宿主函数(提案中)
  • CAP-0075: Poseidon/Poseidon2宿主函数(提案中)
实现前,请务必验证:
  1. CAP前言中的状态(
    Accepted
    /
    Implemented
    vs 草稿/待决策)
  2. 目标网络软件版本和协议支持
  3. soroban-sdk
    版本对目标宿主函数的支持

Practical guidance

实践指南

  • Use BLS12-381 features where supported and documented in your target SDK/network.
  • For BN254/Poseidon plans, design feature flags and graceful fallbacks until support is active.
  • Keep cryptographic assumptions explicit in audits and deployment notes.
  • 在目标SDK/网络支持且文档完善的场景下使用BLS12-381功能
  • 对于BN254/Poseidon计划,设计功能标志和优雅降级方案,直到支持生效
  • 在审计和部署说明中明确 cryptographic 假设

Example references

示例参考

See zk-proofs.md for Groth16 verification patterns, Poseidon usage, Noir/RISC Zero integration, and implementation guidance.

有关Groth16验证模式、Poseidon使用、Noir/RISC Zero集成及实现指南,请参阅zk-proofs.md

Part 2: Testing Strategy

第二部分:测试策略

Quick Navigation

快速导航

Testing Pyramid

测试金字塔

  1. Unit tests (fast): Native Rust tests with
    soroban-sdk
    testutils
  2. Local integration tests: Stellar Quickstart Docker
  3. Testnet tests: Deploy and test on public testnet
  4. Mainnet smoke tests: Final validation before production
  1. 单元测试(快速):使用
    soroban-sdk
    测试工具的原生Rust测试
  2. 本地集成测试:Stellar Quickstart Docker
  3. 测试网测试:在公开测试网部署并测试
  4. 主网冒烟测试:生产前最终验证

Unit Testing with Soroban SDK

使用Soroban SDK进行单元测试

The Soroban SDK provides comprehensive testing utilities that run natively (not in WASM), enabling fast iteration with full debugging support.
Soroban SDK提供全面的测试工具,可原生运行(无需WASM),支持快速迭代和完整调试。

Basic Test Setup

基础测试设置

rust
#![cfg(test)]

use soroban_sdk::{testutils::Address as _, Address, Env};

// Import your contract
use crate::{Contract, ContractClient};

#[test]
fn test_basic_functionality() {
    // Create test environment
    let env = Env::default();

    // Register contract
    let contract_id = env.register_contract(None, Contract);

    // Create typed client
    let client = ContractClient::new(&env, &contract_id);

    // Generate test addresses
    let user = Address::generate(&env);

    // Call contract functions
    client.initialize(&user);

    // Assert results
    assert_eq!(client.get_value(), 0);
}
rust
#![cfg(test)]

use soroban_sdk::{testutils::Address as _, Address, Env};

// 导入你的合约
use crate::{Contract, ContractClient};

#[test]
fn test_basic_functionality() {
    // 创建测试环境
    let env = Env::default();

    // 注册合约
    let contract_id = env.register_contract(None, Contract);

    // 创建类型化客户端
    let client = ContractClient::new(&env, &contract_id);

    // 生成测试地址
    let user = Address::generate(&env);

    // 调用合约函数
    client.initialize(&user);

    // 断言结果
    assert_eq!(client.get_value(), 0);
}

Testing Authorization

测试授权机制

rust
#[test]
fn test_with_auth() {
    let env = Env::default();

    // Mock all authorizations automatically
    env.mock_all_auths();

    let contract_id = env.register_contract(None, TokenContract);
    let client = TokenContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    let user1 = Address::generate(&env);
    let user2 = Address::generate(&env);

    // Initialize and mint
    client.initialize(&admin);
    client.mint(&user1, &1000);

    // Transfer (requires auth from user1)
    client.transfer(&user1, &user2, &100);

    assert_eq!(client.balance(&user1), 900);
    assert_eq!(client.balance(&user2), 100);

    // Verify which auths were required
    let auths = env.auths();
    assert_eq!(auths.len(), 1);
    // auths[0] contains (address, contract_id, function, args)
}
rust
#[test]
fn test_with_auth() {
    let env = Env::default();

    // 自动模拟所有授权
    env.mock_all_auths();

    let contract_id = env.register_contract(None, TokenContract);
    let client = TokenContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    let user1 = Address::generate(&env);
    let user2 = Address::generate(&env);

    // 初始化并铸币
    client.initialize(&admin);
    client.mint(&user1, &1000);

    // 转账(需要user1授权)
    client.transfer(&user1, &user2, &100);

    assert_eq!(client.balance(&user1), 900);
    assert_eq!(client.balance(&user2), 100);

    // 验证所需的授权
    let auths = env.auths();
    assert_eq!(auths.len(), 1);
    // auths[0]包含(地址, 合约ID, 函数, 参数)
}

Testing with Specific Auth Requirements

测试特定授权要求

rust
#[test]
fn test_specific_auth() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    let user = Address::generate(&env);

    // Mock auth only for specific address
    env.mock_auths(&[MockAuth {
        address: &user,
        invoke: &MockAuthInvoke {
            contract: &contract_id,
            fn_name: "transfer",
            args: (&user, &other, &100i128).into_val(&env),
            sub_invokes: &[],
        },
    }]);

    client.transfer(&user, &other, &100);
}
rust
#[test]
fn test_specific_auth() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    let user = Address::generate(&env);

    // 仅模拟特定地址的授权
    env.mock_auths(&[MockAuth {
        address: &user,
        invoke: &MockAuthInvoke {
            contract: &contract_id,
            fn_name: "transfer",
            args: (&user, &other, &100i128).into_val(&env),
            sub_invokes: &[],
        },
    }]);

    client.transfer(&user, &other, &100);
}

Testing Time-Dependent Logic

测试时间相关逻辑

rust
#[test]
fn test_time_based() {
    let env = Env::default();
    let contract_id = env.register_contract(None, VestingContract);
    let client = VestingContractClient::new(&env, &contract_id);

    let beneficiary = Address::generate(&env);

    // Set initial timestamp
    env.ledger().set_timestamp(1000);

    client.create_vesting(&beneficiary, &1000, &2000); // unlock at t=2000

    // Try to claim before unlock
    assert!(client.try_claim(&beneficiary).is_err());

    // Advance time past unlock
    env.ledger().set_timestamp(2500);

    // Now claim succeeds
    client.claim(&beneficiary);
}
rust
#[test]
fn test_time_based() {
    let env = Env::default();
    let contract_id = env.register_contract(None, VestingContract);
    let client = VestingContractClient::new(&env, &contract_id);

    let beneficiary = Address::generate(&env);

    // 设置初始时间戳
    env.ledger().set_timestamp(1000);

    client.create_vesting(&beneficiary, &1000, &2000); // t=2000时解锁

    // 尝试在解锁前领取
    assert!(client.try_claim(&beneficiary).is_err());

    // 将时间推进到解锁后
    env.ledger().set_timestamp(2500);

    // 现在领取成功
    client.claim(&beneficiary);
}

Testing Ledger State

测试账本状态

rust
#[test]
fn test_ledger_manipulation() {
    let env = Env::default();

    // Set ledger sequence
    env.ledger().set_sequence_number(1000);

    // Set timestamp
    env.ledger().set_timestamp(1704067200); // Jan 1, 2024

    // Set network passphrase
    env.ledger().set_network_id([0u8; 32]); // Custom network ID

    // Get current values
    let seq = env.ledger().sequence();
    let ts = env.ledger().timestamp();
}
rust
#[test]
fn test_ledger_manipulation() {
    let env = Env::default();

    // 设置账本序列
    env.ledger().set_sequence_number(1000);

    // 设置时间戳
    env.ledger().set_timestamp(1704067200); // 2024年1月1日

    // 设置网络密码
    env.ledger().set_network_id([0u8; 32]); // 自定义网络ID

    // 获取当前值
    let seq = env.ledger().sequence();
    let ts = env.ledger().timestamp();
}

Testing Events

测试事件

rust
#[test]
fn test_events() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    client.do_something();

    // Get all events
    let events = env.events().all();

    // Check specific event
    assert_eq!(events.len(), 1);

    let event = &events[0];
    // event.0 = contract_id
    // event.1 = topics (Vec<Val>)
    // event.2 = data (Val)
}
rust
#[test]
fn test_events() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    client.do_something();

    // 获取所有事件
    let events = env.events().all();

    // 检查特定事件
    assert_eq!(events.len(), 1);

    let event = &events[0];
    // event.0 = 合约ID
    // event.1 = 主题(Vec<Val>)
    // event.2 = 数据(Val)
}

Testing Storage

测试存储

rust
#[test]
fn test_storage_ttl() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    client.store_data();

    // Check TTL
    let key = DataKey::MyData;
    let ttl = env.as_contract(&contract_id, || {
        env.storage().persistent().get_ttl(&key)
    });

    assert!(ttl > 0);
}
rust
#[test]
fn test_storage_ttl() {
    let env = Env::default();
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(&env, &contract_id);

    client.store_data();

    // 检查TTL
    let key = DataKey::MyData;
    let ttl = env.as_contract(&contract_id, || {
        env.storage().persistent().get_ttl(&key)
    });

    assert!(ttl > 0);
}

Testing Cross-Contract Calls

测试跨合约调用

rust
#[test]
fn test_cross_contract() {
    let env = Env::default();

    // Register both contracts
    let token_id = env.register_contract_wasm(None, token::WASM);
    let vault_id = env.register_contract(None, VaultContract);

    let token_client = token::Client::new(&env, &token_id);
    let vault_client = VaultContractClient::new(&env, &vault_id);

    env.mock_all_auths();

    let user = Address::generate(&env);

    // Setup: mint tokens to user
    token_client.mint(&user, &1000);

    // Test: deposit tokens into vault
    vault_client.deposit(&user, &token_id, &500);

    assert_eq!(token_client.balance(&user), 500);
    assert_eq!(vault_client.balance(&user), 500);
}
rust
#[test]
fn test_cross_contract() {
    let env = Env::default();

    // 注册两个合约
    let token_id = env.register_contract_wasm(None, token::WASM);
    let vault_id = env.register_contract(None, VaultContract);

    let token_client = token::Client::new(&env, &token_id);
    let vault_client = VaultContractClient::new(&env, &vault_id);

    env.mock_all_auths();

    let user = Address::generate(&env);

    // 设置:给用户铸币
    token_client.mint(&user, &1000);

    // 测试:将代币存入金库
    vault_client.deposit(&user, &token_id, &500);

    assert_eq!(token_client.balance(&user), 500);
    assert_eq!(vault_client.balance(&user), 500);
}

Local Testing with Stellar Quickstart

使用Stellar Quickstart进行本地测试

Start Local Network

启动本地网络

bash
undefined
bash
undefined

Pull and run Stellar Quickstart

拉取并运行Stellar Quickstart

docker run --rm -it -p 8000:8000
--name stellar
stellar/quickstart:latest
--local
--enable-soroban-rpc
docker run --rm -it -p 8000:8000
--name stellar
stellar/quickstart:latest
--local
--enable-soroban-rpc

Or use Stellar CLI

或使用Stellar CLI

stellar container start local
undefined
stellar container start local
undefined

Configure for Local Network

配置本地网络

typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const LOCAL_RPC = "http://localhost:8000/soroban/rpc";
const LOCAL_HORIZON = "http://localhost:8000";
const LOCAL_PASSPHRASE = "Standalone Network ; February 2017";

const rpc = new StellarSdk.rpc.Server(LOCAL_RPC);
const horizon = new StellarSdk.Horizon.Server(LOCAL_HORIZON);
typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const LOCAL_RPC = "http://localhost:8000/soroban/rpc";
const LOCAL_HORIZON = "http://localhost:8000";
const LOCAL_PASSPHRASE = "Standalone Network ; February 2017";

const rpc = new StellarSdk.rpc.Server(LOCAL_RPC);
const horizon = new StellarSdk.Horizon.Server(LOCAL_HORIZON);

Fund Test Accounts (Local)

资助测试账户(本地)

bash
undefined
bash
undefined

Using Stellar CLI

使用Stellar CLI

stellar keys generate --global test-account --network local --fund
stellar keys generate --global test-account --network local --fund

Or via friendbot endpoint

或通过friendbot端点

Deploy and Test Locally

本地部署与测试

bash
undefined
bash
undefined

Deploy contract to local network

将合约部署到本地网络

stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source test-account
--network local
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source test-account
--network local

Invoke contract

调用合约

stellar contract invoke
--id CONTRACT_ID
--source test-account
--network local
--
function_name
--arg value
undefined
stellar contract invoke
--id CONTRACT_ID
--source test-account
--network local
--
function_name
--arg value
undefined

Testnet Testing

测试网测试

Network Configuration

网络配置

bash
undefined
bash
undefined

Network Passphrase: "Test SDF Network ; September 2015"

网络密码: "Test SDF Network ; September 2015"

undefined
undefined

Create and Fund Testnet Account

创建并资助测试网账户

bash
undefined
bash
undefined

Generate new identity

生成新身份

stellar keys generate --global my-testnet-key --network testnet
stellar keys generate --global my-testnet-key --network testnet

Fund via Friendbot

通过Friendbot资助

stellar keys fund my-testnet-key --network testnet
stellar keys fund my-testnet-key --network testnet

Or manually

或手动操作

Deploy to Testnet

部署到测试网

bash
undefined
bash
undefined

Deploy contract

部署合约

stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet

Install contract code (separate from deployment)

安装合约代码(与部署分离)

stellar contract install
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
undefined
stellar contract install
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
undefined

Testnet Reset Awareness

测试网重置注意事项

Important: Testnet resets approximately quarterly:
  • All accounts and contracts are deleted
  • Plan for re-deployment after resets
  • Don't rely on persistent state for test data
重要提示:测试网约每季度重置一次:
  • 所有账户和合约将被删除
  • 计划重置后的重新部署
  • 不要依赖测试数据的持久状态
查看重置时间表:https://stellar.org/developers/blog

Integration Testing Patterns

集成测试模式

TypeScript Integration Tests

TypeScript集成测试

typescript
// tests/integration/contract.test.ts
import * as StellarSdk from "@stellar/stellar-sdk";

const RPC_URL = process.env.RPC_URL || "http://localhost:8000/soroban/rpc";
const NETWORK_PASSPHRASE = process.env.NETWORK_PASSPHRASE || "Standalone Network ; February 2017";

describe("Contract Integration Tests", () => {
  let rpc: StellarSdk.rpc.Server;
  let keypair: StellarSdk.Keypair;
  let contractId: string;

  beforeAll(async () => {
    rpc = new StellarSdk.rpc.Server(RPC_URL);
    keypair = StellarSdk.Keypair.random();

    // Fund account
    await fundAccount(keypair.publicKey());

    // Deploy contract
    contractId = await deployContract(keypair);
  });

  test("should initialize contract", async () => {
    const account = await rpc.getAccount(keypair.publicKey());
    const contract = new StellarSdk.Contract(contractId);

    const tx = new StellarSdk.TransactionBuilder(account, {
      fee: "100",
      networkPassphrase: NETWORK_PASSPHRASE,
    })
      .addOperation(
        contract.call(
          "initialize",
          StellarSdk.Address.fromString(keypair.publicKey()).toScVal()
        )
      )
      .setTimeout(30)
      .build();

    const simResult = await rpc.simulateTransaction(tx);
    const preparedTx = StellarSdk.rpc.assembleTransaction(tx, simResult);

    preparedTx.sign(keypair);
    const result = await rpc.sendTransaction(preparedTx.build());

    expect(result.status).not.toBe("ERROR");
  });
});
typescript
// tests/integration/contract.test.ts
import * as StellarSdk from "@stellar/stellar-sdk";

const RPC_URL = process.env.RPC_URL || "http://localhost:8000/soroban/rpc";
const NETWORK_PASSPHRASE = process.env.NETWORK_PASSPHRASE || "Standalone Network ; February 2017";

describe("Contract Integration Tests", () => {
  let rpc: StellarSdk.rpc.Server;
  let keypair: StellarSdk.Keypair;
  let contractId: string;

  beforeAll(async () => {
    rpc = new StellarSdk.rpc.Server(RPC_URL);
    keypair = StellarSdk.Keypair.random();

    // 资助账户
    await fundAccount(keypair.publicKey());

    // 部署合约
    contractId = await deployContract(keypair);
  });

  test("should initialize contract", async () => {
    const account = await rpc.getAccount(keypair.publicKey());
    const contract = new StellarSdk.Contract(contractId);

    const tx = new StellarSdk.TransactionBuilder(account, {
      fee: "100",
      networkPassphrase: NETWORK_PASSPHRASE,
    })
      .addOperation(
        contract.call(
          "initialize",
          StellarSdk.Address.fromString(keypair.publicKey()).toScVal()
        )
      )
      .setTimeout(30)
      .build();

    const simResult = await rpc.simulateTransaction(tx);
    const preparedTx = StellarSdk.rpc.assembleTransaction(tx, simResult);

    preparedTx.sign(keypair);
    const result = await rpc.sendTransaction(preparedTx.build());

    expect(result.status).not.toBe("ERROR");
  });
});

Rust Integration Tests

Rust集成测试

rust
// tests/integration_test.rs
use soroban_sdk::{Env, Address};
use std::process::Command;

#[test]
#[ignore] // Run with: cargo test -- --ignored
fn integration_test_with_local_network() {
    // Requires local network running
    let output = Command::new("stellar")
        .args([
            "contract", "invoke",
            "--id", "CONTRACT_ID",
            "--source", "test-account",
            "--network", "local",
            "--",
            "get_count"
        ])
        .output()
        .expect("Failed to invoke contract");

    assert!(output.status.success());
}
rust
// tests/integration_test.rs
use soroban_sdk::{Env, Address};
use std::process::Command;

#[test]
#[ignore] // 使用以下命令运行: cargo test -- --ignored
fn integration_test_with_local_network() {
    // 需要本地网络正在运行
    let output = Command::new("stellar")
        .args([
            "contract", "invoke",
            "--id", "CONTRACT_ID",
            "--source", "test-account",
            "--network", "local",
            "--",
            "get_count"
        ])
        .output()
        .expect("Failed to invoke contract");

    assert!(output.status.success());
}

Test Configuration

测试配置

Cargo.toml for Tests

测试用Cargo.toml

toml
[dev-dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }  # match [dependencies] version

[profile.test]
opt-level = 0
debug = true
toml
[dev-dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }  # 与[dependencies]版本匹配

[profile.test]
opt-level = 0
debug = true

Running Tests

运行测试

bash
undefined
bash
undefined

Run unit tests

运行单元测试

cargo test
cargo test

Run with output

显示输出

cargo test -- --nocapture
cargo test -- --nocapture

Run specific test

运行特定测试

cargo test test_transfer
cargo test test_transfer

Run ignored (integration) tests

运行忽略的(集成)测试

cargo test -- --ignored
undefined
cargo test -- --ignored
undefined

CI/CD Configuration

CI/CD配置

GitHub Actions Example

GitHub Actions示例

yaml
name: Test Soroban Contract

on: [push, pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Add WASM target
        run: rustup target add wasm32-unknown-unknown

      - name: Run unit tests
        run: cargo test

      - name: Build contract
        run: cargo build --release --target wasm32-unknown-unknown

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    services:
      stellar:
        image: stellar/quickstart:latest
        ports:
          - 8000:8000
        options: >-
          --health-cmd "curl -f http://localhost:8000 || exit 1"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 10

    steps:
      - uses: actions/checkout@v4

      - name: Install Stellar CLI
        run: |
          cargo install stellar-cli --locked

      - name: Deploy and test
        run: |
          stellar keys generate --global ci-test --network local --fund
          stellar contract deploy \
            --wasm target/wasm32-unknown-unknown/release/contract.wasm \
            --source ci-test \
            --network local
yaml
name: Test Soroban Contract

on: [push, pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Add WASM target
        run: rustup target add wasm32-unknown-unknown

      - name: Run unit tests
        run: cargo test

      - name: Build contract
        run: cargo build --release --target wasm32-unknown-unknown

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    services:
      stellar:
        image: stellar/quickstart:latest
        ports:
          - 8000:8000
        options: >-
          --health-cmd "curl -f http://localhost:8000 || exit 1"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 10

    steps:
      - uses: actions/checkout@v4

      - name: Install Stellar CLI
        run: |
          cargo install stellar-cli --locked

      - name: Deploy and test
        run: |
          stellar keys generate --global ci-test --network local --fund
          stellar contract deploy \
            --wasm target/wasm32-unknown-unknown/release/contract.wasm \
            --source ci-test \
            --network local

Best Practices

最佳实践

Test Organization

测试组织

project/
├── src/
│   └── lib.rs
├── tests/
│   ├── common/
│   │   └── mod.rs      # Shared test utilities
│   ├── unit/
│   │   ├── mod.rs
│   │   └── transfer.rs
│   └── integration/
│       └── full_flow.rs
└── Cargo.toml
project/
├── src/
│   └── lib.rs
├── tests/
│   ├── common/
│   │   └── mod.rs      # 共享测试工具
│   ├── unit/
│   │   ├── mod.rs
│   │   └── transfer.rs
│   └── integration/
│       └── full_flow.rs
└── Cargo.toml

Test Utilities Module

测试工具模块

rust
// tests/common/mod.rs
use soroban_sdk::{testutils::Address as _, Address, Env};
use crate::{Contract, ContractClient};

pub fn setup_contract(env: &Env) -> (Address, ContractClient) {
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(env, &contract_id);
    let admin = Address::generate(env);

    env.mock_all_auths();
    client.initialize(&admin);

    (contract_id, client)
}

pub fn create_funded_user(env: &Env, client: &ContractClient, amount: i128) -> Address {
    let user = Address::generate(env);
    client.mint(&user, &amount);
    user
}
rust
// tests/common/mod.rs
use soroban_sdk::{testutils::Address as _, Address, Env};
use crate::{Contract, ContractClient};

pub fn setup_contract(env: &Env) -> (Address, ContractClient) {
    let contract_id = env.register_contract(None, Contract);
    let client = ContractClient::new(env, &contract_id);
    let admin = Address::generate(env);

    env.mock_all_auths();
    client.initialize(&admin);

    (contract_id, client)
}

pub fn create_funded_user(env: &Env, client: &ContractClient, amount: i128) -> Address {
    let user = Address::generate(env);
    client.mint(&user, &amount);
    user
}

Fuzz Testing

模糊测试

Soroban has first-class fuzz testing via
cargo-fuzz
and the built-in
SorobanArbitrary
trait. All
#[contracttype]
types automatically derive
SorobanArbitrary
when the
"testutils"
feature is active.
Soroban通过
cargo-fuzz
和内置的
SorobanArbitrary
特性提供一流的模糊测试支持。当
"testutils"
特性启用时,所有
#[contracttype]
类型会自动派生
SorobanArbitrary

Setup

设置

bash
undefined
bash
undefined

Install nightly Rust + cargo-fuzz

安装nightly Rust + cargo-fuzz

rustup install nightly cargo install --locked cargo-fuzz
rustup install nightly cargo install --locked cargo-fuzz

Initialize fuzz targets

初始化模糊测试目标

cargo fuzz init

Update `Cargo.toml` to include both crate types:
```toml
[lib]
crate-type = ["lib", "cdylib"]
Add to
fuzz/Cargo.toml
:
toml
[dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }
cargo fuzz init

更新`Cargo.toml`以包含两种 crate 类型:
```toml
[lib]
crate-type = ["lib", "cdylib"]
fuzz/Cargo.toml
中添加:
toml
[dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }

Writing a Fuzz Target

编写模糊测试目标

rust
// fuzz/fuzz_targets/fuzz_deposit.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use soroban_sdk::{testutils::Address as _, Address, Env};
use my_contract::{Contract, ContractClient};

fuzz_target!(|input: (u64, i128)| {
    let (seed, amount) = input;
    let env = Env::default();
    env.mock_all_auths();

    let contract_id = env.register(Contract, ());
    let client = ContractClient::new(&env, &contract_id);
    let user = Address::generate(&env);

    // Initialize
    client.initialize(&user);

    // Fuzz deposit — should never panic unexpectedly
    let _ = client.try_deposit(&user, &amount);
});
rust
// fuzz/fuzz_targets/fuzz_deposit.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use soroban_sdk::{testutils::Address as _, Address, Env};
use my_contract::{Contract, ContractClient};

fuzz_target!(|input: (u64, i128)| {
    let (seed, amount) = input;
    let env = Env::default();
    env.mock_all_auths();

    let contract_id = env.register(Contract, ());
    let client = ContractClient::new(&env, &contract_id);
    let user = Address::generate(&env);

    // 初始化
    client.initialize(&user);

    // 模糊测试存款操作——不应意外panic
    let _ = client.try_deposit(&user, &amount);
});

Running Fuzz Tests

运行模糊测试

bash
undefined
bash
undefined

Run (use --sanitizer=thread on macOS)

运行(macOS使用--sanitizer=thread)

cargo +nightly fuzz run fuzz_deposit
cargo +nightly fuzz run fuzz_deposit

Generate code coverage

生成代码覆盖率

cargo +nightly fuzz coverage fuzz_deposit
undefined
cargo +nightly fuzz coverage fuzz_deposit
undefined

Soroban Token Fuzzer

Soroban代币模糊测试器

Reusable library for fuzzing token contracts:
可复用的代币合约模糊测试库:

Documentation

文档

Property-Based Testing

属性测试

Use
proptest
with
SorobanArbitrary
for QuickCheck-style property testing that runs in standard
cargo test
.
rust
#[cfg(test)]
mod prop_tests {
    use super::*;
    use proptest::prelude::*;
    use soroban_sdk::{testutils::Address as _, Env};

    proptest! {
        #[test]
        fn deposit_then_withdraw_preserves_balance(amount in 1i128..=i128::MAX) {
            let env = Env::default();
            env.mock_all_auths();
            let contract_id = env.register(Contract, ());
            let client = ContractClient::new(&env, &contract_id);
            let user = Address::generate(&env);

            client.initialize(&user);
            client.deposit(&user, &amount);
            client.withdraw(&user, &amount);

            prop_assert_eq!(client.balance(&user), 0);
        }
    }
}
Recommended workflow: Use
cargo-fuzz
interactively to find deep bugs, then convert to
proptest
for regression prevention in CI.
使用
proptest
结合
SorobanArbitrary
进行QuickCheck风格的属性测试,可在标准
cargo test
中运行。
rust
#[cfg(test)]
mod prop_tests {
    use super::*;
    use proptest::prelude::*;
    use soroban_sdk::{testutils::Address as _, Env};

    proptest! {
        #[test]
        fn deposit_then_withdraw_preserves_balance(amount in 1i128..=i128::MAX) {
            let env = Env::default();
            env.mock_all_auths();
            let contract_id = env.register(Contract, ());
            let client = ContractClient::new(&env, &contract_id);
            let user = Address::generate(&env);

            client.initialize(&user);
            client.deposit(&user, &amount);
            client.withdraw(&user, &amount);

            prop_assert_eq!(client.balance(&user), 0);
        }
    }
}
推荐工作流:使用
cargo-fuzz
交互式查找深层漏洞,然后转换为
proptest
在CI中预防回归。

Differential Testing with Test Snapshots

使用测试快照的差分测试

Soroban automatically writes JSON snapshots at the end of every test to
test_snapshots/
, capturing events and final ledger state. Commit these to source control — diffs reveal unintended behavioral changes.
Soroban会在每次测试结束时自动将事件和最终账本状态写入JSON快照到
test_snapshots/
目录。将这些快照提交到版本控制——差异会显示意外的行为变化。

Comparing Against Deployed Contracts

与已部署合约对比

rust
// Fetch deployed contract for comparison
// $ stellar contract fetch --id C... --out-file deployed.wasm

mod deployed {
    soroban_sdk::contractimport!(file = "deployed.wasm");
}

#[test]
fn test_upgrade_compatibility() {
    let env = Env::default();
    env.mock_all_auths();

    // Register both versions
    let old_id = env.register_contract_wasm(None, deployed::WASM);
    let new_id = env.register(NewContract, ());

    let old_client = deployed::Client::new(&env, &old_id);
    let new_client = NewContractClient::new(&env, &new_id);

    let user = Address::generate(&env);

    // Run identical operations and compare
    old_client.initialize(&user);
    new_client.initialize(&user);

    assert_eq!(old_client.get_value(), new_client.get_value());
}
rust
// 获取已部署合约用于对比
// $ stellar contract fetch --id C... --out-file deployed.wasm

mod deployed {
    soroban_sdk::contractimport!(file = "deployed.wasm");
}

#[test]
fn test_upgrade_compatibility() {
    let env = Env::default();
    env.mock_all_auths();

    // 注册两个版本
    let old_id = env.register_contract_wasm(None, deployed::WASM);
    let new_id = env.register(NewContract, ());

    let old_client = deployed::Client::new(&env, &old_id);
    let new_client = NewContractClient::new(&env, &new_id);

    let user = Address::generate(&env);

    // 运行相同操作并对比
    old_client.initialize(&user);
    new_client.initialize(&user);

    assert_eq!(old_client.get_value(), new_client.get_value());
}

Fork Testing

分叉测试

Test against real production state using ledger snapshots:
bash
undefined
使用账本快照针对真实生产状态进行测试:
bash
undefined

Create snapshot of deployed contract

创建已部署合约的快照

stellar snapshot create --address C... --output json --out snapshot.json
stellar snapshot create --address C... --output json --out snapshot.json

Optionally at a specific ledger

可选:在特定账本高度创建

stellar snapshot create --address C... --ledger 12345678 --output json --out snapshot.json

```rust
#[test]
fn test_against_mainnet_state() {
    let env = Env::from_ledger_snapshot_file("snapshot.json");
    env.mock_all_auths();

    let contract_id = /* contract address from snapshot */;
    let client = ContractClient::new(&env, &contract_id);

    // Test operations against real state
    let result = client.get_value();
    assert!(result > 0);
}
stellar snapshot create --address C... --ledger 12345678 --output json --out snapshot.json

```rust
#[test]
fn test_against_mainnet_state() {
    let env = Env::from_ledger_snapshot_file("snapshot.json");
    env.mock_all_auths();

    let contract_id = /* 快照中的合约地址 */;
    let client = ContractClient::new(&env, &contract_id);

    // 针对真实状态测试操作
    let result = client.get_value();
    assert!(result > 0);
}

Mutation Testing

变异测试

Use
cargo-mutants
to verify test quality — modifies source code and checks that tests catch the changes.
bash
cargo install --locked cargo-mutants
cargo mutants
Output interpretation:
  • CAUGHT: Tests detected the mutation (good coverage)
  • MISSED: Tests passed despite mutation (test gap — review
    mutants.out/diff/
    )
使用
cargo-mutants
验证测试质量——修改源代码并检查测试是否能捕获变化。
bash
cargo install --locked cargo-mutants
cargo mutants
输出解读:
  • CAUGHT: 测试检测到变异(覆盖率良好)
  • MISSED: 测试在变异后仍通过(测试存在漏洞——查看
    mutants.out/diff/
  • 文档: 变异测试

Resource Profiling

资源分析

Soroban uses a multidimensional resource model (CPU instructions, ledger reads/writes, bytes, events, rent).
Soroban使用多维资源模型(CPU指令、账本读写、字节数、事件、租金)。

CLI Simulation

CLI模拟

bash
undefined
bash
undefined

Simulate contract invocation to see resource costs

模拟合约调用查看资源成本

stellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
undefined
stellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
undefined

Stellar Plus Profiler (Cheesecake Labs)

Stellar Plus分析器(Cheesecake Labs)

typescript
import { StellarPlus } from 'stellar-plus';

const profilerPlugin = new StellarPlus.Utils.Plugins.sorobanTransaction.profiler();
// Collects CPU instructions, RAM, ledger reads/writes
// Aggregation: sum, average, standard deviation
// Output: CSV, formatted text tables
typescript
import { StellarPlus } from 'stellar-plus';

const profilerPlugin = new StellarPlus.Utils.Plugins.sorobanTransaction.profiler();
// 收集CPU指令、RAM、账本读写
// 聚合:总和、平均值、标准差
// 输出:CSV、格式化文本表格

Testing Checklist

测试检查清单

  • Unit tests cover all public functions
  • Edge cases tested (zero amounts, max values, empty state)
  • Authorization tested (correct signers required)
  • Error conditions tested (invalid inputs, unauthorized)
  • Events emission verified
  • Storage TTL behavior validated
  • Cross-contract interactions tested
  • Fuzz tests for critical paths (deposits, withdrawals, swaps)
  • Property-based tests for invariants
  • Mutation testing confirms test quality
  • Differential test snapshots committed to source control
  • Integration tests against local network
  • Testnet deployment verified before mainnet

  • 单元测试覆盖所有公共函数
  • 测试边缘情况(零金额、最大值、空状态)
  • 测试授权(验证正确签名者)
  • 测试错误条件(无效输入、未授权)
  • 验证事件触发
  • 验证存储TTL行为
  • 测试跨合约交互
  • 对关键路径进行模糊测试(存款、取款、交换)
  • 对不变量进行属性测试
  • 变异测试确认测试质量
  • 将差分测试快照提交到版本控制
  • 针对本地网络进行集成测试
  • 主网部署前验证测试网部署

Part 3: Security

第三部分:安全

Core Principle

核心原则

Assume the attacker controls:
  • All arguments passed to contract functions
  • Transaction ordering and timing
  • All accounts except those requiring signatures
  • The ability to create contracts that mimic your interface
假设攻击者可以控制:
  • 传递给合约函数的所有参数
  • 交易顺序和时间
  • 除需要签名的账户外的所有账户
  • 创建模仿你合约接口的恶意合约的能力

Soroban Security Advantages

Soroban安全优势

Soroban's architecture prevents certain vulnerability classes by design:
Soroban的架构从设计上防止了某些类型的漏洞:

No Delegate Call

无Delegate Call

Unlike Ethereum, Soroban has no
delegatecall
equivalent. Contracts cannot execute arbitrary bytecode in their context, eliminating proxy-based attacks.
与Ethereum不同,Soroban没有
delegatecall
等价功能。合约无法在自身上下文中执行任意字节码,消除了代理攻击。

No Classical Reentrancy

无经典重入

Soroban's synchronous execution model prevents the cross-contract reentrancy that plagues Ethereum. Self-reentrancy is possible but rarely exploitable.
Soroban的同步执行模型防止了困扰Ethereum的跨合约重入。自重重入是可能的,但很少可被利用。

Explicit Authorization

显式授权

Authorization is opt-in via
require_auth()
, making it explicit which operations need signatures.

授权通过
require_auth()
选择启用,明确哪些操作需要签名。

Vulnerability Categories

漏洞类别

1. Missing Authorization Checks

1. 缺失授权检查

Risk: Anyone can call privileged functions without proper verification.
Attack: Attacker calls admin-only functions, drains funds, or modifies critical state.
Vulnerable Code:
rust
// BAD: No authorization check
pub fn withdraw(env: Env, to: Address, amount: i128) {
    transfer_tokens(&env, &to, amount);
}
Secure Code:
rust
// GOOD: Requires authorization from admin
pub fn withdraw(env: Env, to: Address, amount: i128) {
    let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
    admin.require_auth();
    transfer_tokens(&env, &to, amount);
}
Prevention: Always use
require_auth()
on the caller or an admin address. See Part 1: Contract Development above for full authorization patterns (direct auth, admin helpers,
require_auth_for_args
).

风险:任何人无需验证即可调用特权函数。
攻击:攻击者调用管理员专属函数、窃取资金或修改关键状态。
漏洞代码:
rust
// 错误:无授权检查
pub fn withdraw(env: Env, to: Address, amount: i128) {
    transfer_tokens(&env, &to, amount);
}
安全代码:
rust
// 正确:要求管理员授权
pub fn withdraw(env: Env, to: Address, amount: i128) {
    let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
    admin.require_auth();
    transfer_tokens(&env, &to, amount);
}
预防措施:始终对调用者或管理员地址使用
require_auth()
。请参阅第一部分:合约开发中的完整授权模式(直接授权、管理员助手、
require_auth_for_args
)。

2. Reinitialization Attacks

2. 重复初始化攻击

Risk: Initialization function can be called multiple times, allowing attacker to overwrite admin or critical state.
Attack: Attacker reinitializes contract to become the admin, then drains assets.
Vulnerable Code:
rust
// BAD: Can be called multiple times
pub fn initialize(env: Env, admin: Address) {
    env.storage().instance().set(&DataKey::Admin, &admin);
}
Secure Code:
rust
// GOOD: Prevents reinitialization
pub fn initialize(env: Env, admin: Address) {
    if env.storage().instance().has(&DataKey::Initialized) {
        panic!("already initialized");
    }
    env.storage().instance().set(&DataKey::Admin, &admin);
    env.storage().instance().set(&DataKey::Initialized, &true);
}

// Alternative: Check for admin existence
pub fn initialize(env: Env, admin: Address) {
    if env.storage().instance().has(&DataKey::Admin) {
        panic!("already initialized");
    }
    env.storage().instance().set(&DataKey::Admin, &admin);
}

风险:初始化函数可被多次调用,允许攻击者覆盖管理员或关键状态。
攻击:攻击者重新初始化合约成为管理员,然后窃取资产。
漏洞代码:
rust
// 错误:可被多次调用
pub fn initialize(env: Env, admin: Address) {
    env.storage().instance().set(&DataKey::Admin, &admin);
}
安全代码:
rust
// 正确:防止重复初始化
pub fn initialize(env: Env, admin: Address) {
    if env.storage().instance().has(&DataKey::Initialized) {
        panic!("already initialized");
    }
    env.storage().instance().set(&DataKey::Admin, &admin);
    env.storage().instance().set(&DataKey::Initialized, &true);
}

// 替代方案:检查管理员是否存在
pub fn initialize(env: Env, admin: Address) {
    if env.storage().instance().has(&DataKey::Admin) {
        panic!("already initialized");
    }
    env.storage().instance().set(&DataKey::Admin, &admin);
}

3. Arbitrary Contract Calls

3. 任意合约调用

Risk: Contract calls whatever address is passed as parameter without validation.
Attack: Attacker passes malicious contract that mimics expected interface but behaves differently.
Vulnerable Code:
rust
// BAD: Calls any contract passed as parameter
pub fn swap(env: Env, token: Address, amount: i128) {
    let client = token::Client::new(&env, &token);
    client.transfer(...); // Could be malicious contract
}
Secure Code:
rust
// GOOD: Validate against known allowlist
pub fn swap(env: Env, token: Address, amount: i128) {
    let allowed_tokens: Vec<Address> = env.storage()
        .instance()
        .get(&DataKey::AllowedTokens)
        .unwrap();

    if !allowed_tokens.contains(&token) {
        panic!("token not allowed");
    }

    let client = token::Client::new(&env, &token);
    client.transfer(...);
}

// Or validate against Stellar Asset Contract
pub fn swap_sac(env: Env, asset: Address, amount: i128) {
    // SACs have known, predictable addresses
    // Verify it's a legitimate SAC if needed
}

风险:合约调用作为参数传递的任意地址,未进行验证。
攻击:攻击者传递模仿预期接口但行为恶意的合约。
漏洞代码:
rust
// 错误:调用作为参数传递的任意合约
pub fn swap(env: Env, token: Address, amount: i128) {
    let client = token::Client::new(&env, &token);
    client.transfer(...); // 可能是恶意合约
}
安全代码:
rust
// 正确:针对已知允许列表验证
pub fn swap(env: Env, token: Address, amount: i128) {
    let allowed_tokens: Vec<Address> = env.storage()
        .instance()
        .get(&DataKey::AllowedTokens)
        .unwrap();

    if !allowed_tokens.contains(&token) {
        panic!("token not allowed");
    }

    let client = token::Client::new(&env, &token);
    client.transfer(...);
}

// 或针对Stellar资产合约验证
pub fn swap_sac(env: Env, asset: Address, amount: i128) {
    // SAC具有已知的可预测地址
    // 如有需要,验证其是否为合法SAC
}

4. Integer Overflow/Underflow

4. 整数溢出/下溢

Risk: Arithmetic operations overflow or underflow, causing unexpected values.
Attack: Attacker manipulates amounts to cause overflow, bypassing balance checks.
Vulnerable Code:
rust
// BAD: Unchecked arithmetic
pub fn deposit(env: Env, user: Address, amount: i128) {
    let balance: i128 = get_balance(&env, &user);
    set_balance(&env, &user, balance + amount); // Can overflow
}
Secure Code:
rust
// GOOD: Use checked arithmetic
pub fn deposit(env: Env, user: Address, amount: i128) {
    let balance: i128 = get_balance(&env, &user);
    let new_balance = balance.checked_add(amount)
        .expect("overflow");
    set_balance(&env, &user, new_balance);
}

// Also validate inputs
pub fn deposit(env: Env, user: Address, amount: i128) {
    if amount <= 0 {
        panic!("invalid amount");
    }
    // ... rest of logic
}

风险:算术操作溢出或下溢,导致意外值。
攻击:攻击者操纵金额导致溢出,绕过余额检查。
漏洞代码:
rust
// 错误:未检查算术操作
pub fn deposit(env: Env, user: Address, amount: i128) {
    let balance: i128 = get_balance(&env, &user);
    set_balance(&env, &user, balance + amount); // 可能溢出
}
安全代码:
rust
// 正确:使用检查式算术
pub fn deposit(env: Env, user: Address, amount: i128) {
    let balance: i128 = get_balance(&env, &user);
    let new_balance = balance.checked_add(amount)
        .expect("overflow");
    set_balance(&env, &user, new_balance);
}

// 同时验证输入
pub fn deposit(env: Env, user: Address, amount: i128) {
    if amount <= 0 {
        panic!("invalid amount");
    }
    // ... 剩余逻辑
}

5. Storage Key Collisions

5. 存储键冲突

Risk: Different data types share the same storage key, causing data corruption.
Attack: Attacker manipulates one type of data to corrupt another.
Vulnerable Code:
rust
// BAD: Same prefix for different data
env.storage().persistent().set(&symbol_short!("data"), &user_balance);
env.storage().persistent().set(&symbol_short!("data"), &config); // Overwrites!
Secure Code:
rust
// GOOD: Use typed enum for keys
#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Balance(Address),
    Config,
    Allowance(Address, Address),
}

env.storage().persistent().set(&DataKey::Balance(user), &balance);
env.storage().instance().set(&DataKey::Config, &config);

风险:不同数据类型共享相同存储键,导致数据损坏。
攻击:攻击者操纵一种数据类型以损坏另一种。
漏洞代码:
rust
// 错误:不同数据使用相同前缀
env.storage().persistent().set(&symbol_short!("data"), &user_balance);
env.storage().persistent().set(&symbol_short!("data"), &config); // 覆盖!
安全代码:
rust
// 正确:使用类型化枚举作为键
#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    Balance(Address),
    Config,
    Allowance(Address, Address),
}

env.storage().persistent().set(&DataKey::Balance(user), &balance);
env.storage().instance().set(&DataKey::Config, &config);

6. Timing/State Race Conditions

6. 时间/状态竞争条件

Risk: Contract state changes between check and use.
Attack: In multi-transaction scenarios, attacker exploits gap between validation and action.
Prevention:
rust
// Use atomic operations where possible
pub fn swap(env: Env, user: Address, amount_in: i128, min_out: i128) {
    user.require_auth();

    // Perform all checks and state changes atomically
    let balance = get_balance(&env, &user);
    if balance < amount_in {
        panic!("insufficient balance");
    }

    let amount_out = calculate_output(amount_in);
    if amount_out < min_out {
        panic!("slippage exceeded");
    }

    // Update all state together
    set_balance(&env, &user, balance - amount_in);
    transfer_output(&env, &user, amount_out);
}

风险:合约状态在检查和使用之间发生变化。
攻击:在多交易场景中,攻击者利用验证和操作之间的间隙。
预防措施:
rust
// 尽可能使用原子操作
pub fn swap(env: Env, user: Address, amount_in: i128, min_out: i128) {
    user.require_auth();

    // 原子性执行所有检查和状态变更
    let balance = get_balance(&env, &user);
    if balance < amount_in {
        panic!("insufficient balance");
    }

    let amount_out = calculate_output(amount_in);
    if amount_out < min_out {
        panic!("slippage exceeded");
    }

    // 一起更新所有状态
    set_balance(&env, &user, balance - amount_in);
    transfer_output(&env, &user, amount_out);
}

7. TTL/Archival Vulnerabilities

7. TTL/归档漏洞

Risk: Critical contract data gets archived, breaking functionality.
Attack: Attacker waits for data to be archived, then exploits the missing state.
Prevention:
rust
// Extend TTL for critical data
pub fn critical_operation(env: Env) {
    // Always extend instance storage
    env.storage().instance().extend_ttl(
        100,      // threshold
        518400,   // extend_to (~30 days)
    );

    // Extend specific persistent keys
    env.storage().persistent().extend_ttl(
        &DataKey::CriticalData,
        100,
        518400,
    );
}

// Consider restoration costs in design
// Archived data can be restored, but requires transaction

风险:关键合约数据被归档,导致功能失效。
攻击:攻击者等待数据被归档,然后利用缺失的状态。
预防措施:
rust
// 主动延长关键数据的TTL
pub fn critical_operation(env: Env) {
    // 始终延长实例存储的TTL
    env.storage().instance().extend_ttl(
        100,      // 阈值
        518400,   // 延长至约30天
    );

    // 延长特定持久键的TTL
    env.storage().persistent().extend_ttl(
        &DataKey::CriticalData,
        100,
        518400,
    );
}

// 在设计中考虑恢复成本
// 归档的数据可以恢复,但需要交易

8. Cross-Contract Call Validation

8. 跨合约调用验证

Risk: Trusting return values from untrusted contracts.
Attack: Malicious contract returns false data, causing incorrect state updates.
Prevention:
rust
// Validate all external data
pub fn process_oracle_price(env: Env, oracle: Address, asset: Address) -> i128 {
    // Validate oracle is trusted
    let trusted_oracles: Vec<Address> = env.storage()
        .instance()
        .get(&DataKey::TrustedOracles)
        .unwrap();

    if !trusted_oracles.contains(&oracle) {
        panic!("untrusted oracle");
    }

    let price: i128 = oracle_client.get_price(&asset);

    // Sanity check the value
    if price <= 0 || price > MAX_REASONABLE_PRICE {
        panic!("invalid price");
    }

    price
}

风险:信任来自未受信任合约的返回值。
攻击:恶意合约返回虚假数据,导致错误的状态更新。
预防措施:
rust
// 验证所有外部数据
pub fn process_oracle_price(env: Env, oracle: Address, asset: Address) -> i128 {
    // 验证预言机是否受信任
    let trusted_oracles: Vec<Address> = env.storage()
        .instance()
        .get(&DataKey::TrustedOracles)
        .unwrap();

    if !trusted_oracles.contains(&oracle) {
        panic!("untrusted oracle");
    }

    let price: i128 = oracle_client.get_price(&asset);

    // 对值进行合理性检查
    if price <= 0 || price > MAX_REASONABLE_PRICE {
        panic!("invalid price");
    }

    price
}

Classic Stellar Security

经典Stellar安全

Trustline Attacks

信任线攻击

Risk: Users create trustlines to malicious assets that look legitimate.
Prevention:
  • Verify asset issuer before creating trustlines
  • Use well-known asset lists (stellar.toml)
  • Display full asset code + issuer in UIs
风险:用户创建指向看似合法的恶意资产的信任线。
预防措施:
  • 创建信任线前验证资产发行方
  • 使用知名资产列表(stellar.toml)
  • 在UI中显示完整资产代码+发行方

Clawback Awareness

回溯功能注意事项

Risk: Assets with clawback enabled can be seized by issuer.
Prevention:
typescript
// Check if clawback is enabled
const issuerAccount = await server.loadAccount(asset.issuer);
const clawbackEnabled = issuerAccount.flags.auth_clawback_enabled;

if (clawbackEnabled) {
  // Warn user or reject asset
}
风险:启用回溯功能的资产可被发行方没收。
预防措施:
typescript
// 检查是否启用回溯功能
const issuerAccount = await server.loadAccount(asset.issuer);
const clawbackEnabled = issuerAccount.flags.auth_clawback_enabled;

if (clawbackEnabled) {
  // 警告用户或拒绝资产
}

Account Merge Attacks

账户合并攻击

Risk: Merged accounts can be recreated with different configuration.
Prevention:
  • Validate account state before critical operations
  • Don't cache account data long-term

风险:合并后的账户可被重新创建并配置不同设置。
预防措施:
  • 关键操作前验证账户状态
  • 不要长期缓存账户数据

Soroban-Specific Checklist

Soroban特定检查清单

Contract Development

合约开发

  • All privileged functions require appropriate authorization
  • Initialization can only happen once
  • External contract calls are validated against allowlists
  • All arithmetic uses checked operations
  • Storage keys are typed and collision-free
  • Critical data TTLs are extended proactively
  • Input validation on all public functions
  • Events emitted for auditable state changes
  • 所有特权函数要求适当授权
  • 初始化只能执行一次
  • 外部合约调用针对允许列表验证
  • 所有算术操作使用检查式操作
  • 存储键为类型化且无冲突
  • 关键数据TTL被主动延长
  • 所有公共函数进行输入验证
  • 为可审计的状态变更触发事件

Storage Security

存储安全

  • Sensitive data uses appropriate storage type
  • Instance storage for shared/admin data
  • Persistent storage for user-specific data
  • Temporary storage only for truly temporary data
  • TTL management strategy documented
  • 敏感数据使用合适的存储类型
  • 实例存储用于共享/管理员数据
  • 持久存储用于用户特定数据
  • 临时存储仅用于真正临时的数据
  • TTL管理策略已文档化

Cross-Contract Calls

跨合约调用

  • Called contracts are validated or allowlisted
  • Return values are sanity-checked
  • Failure cases handled gracefully
  • No excessive trust in external state

  • 被调用合约已验证或在允许列表中
  • 返回值已进行合理性检查
  • 失败情况已优雅处理
  • 不过度信任外部状态

Client-Side Checklist

客户端检查清单

  • Network passphrase validated before signing
  • Transaction simulation before submission
  • Clear display of all operation details
  • Confirmation for high-value transactions
  • Handle all error states gracefully
  • Don't trust client-side validation alone
  • Verify contract addresses against known deployments
  • Check asset trustline status before transfers

  • 签名前验证网络密码
  • 提交前模拟交易
  • 清晰显示所有操作细节
  • 高价值交易需要确认
  • 优雅处理所有错误状态
  • 不单独依赖客户端验证
  • 针对已知部署验证合约地址
  • 转账前检查资产信任线状态

Security Review Questions

安全审查问题

  1. Can anyone call admin functions without authorization?
  2. Can the contract be reinitialized?
  3. Are all external contract calls validated?
  4. Is arithmetic safe from overflow/underflow?
  5. Can storage keys collide?
  6. Will critical data survive archival?
  7. Are cross-contract return values validated?
  8. Can timing attacks exploit state changes?

  1. 任何人无需授权即可调用管理员函数吗?
  2. 合约可被重新初始化吗?
  3. 所有外部合约调用都经过验证吗?
  4. 算术操作是否安全,无溢出/下溢?
  5. 存储键可能冲突吗?
  6. 关键数据能在归档后存活吗?
  7. 跨合约返回值已验证吗?
  8. 时间攻击能利用状态变化吗?

Bug Bounty Programs

漏洞赏金计划

Immunefi — Stellar Core (up to $250K)

Immunefi — Stellar核心(最高$250K)

  • URL: https://immunefi.com/bug-bounty/stellar/
  • Scope: stellar-core, rs-soroban-sdk, rs-soroban-env, soroban-tools (CLI + RPC), js-soroban-client, rs-stellar-xdr, wasmi fork
  • Rewards: Critical $50K–$250K, High $10K–$50K, Medium $5K, Low $1K
  • Payment: USD-denominated, paid in XLM. KYC required.
  • Rules: PoC required. Test on local forks only (no mainnet/testnet).
  • URL: https://immunefi.com/bug-bounty/stellar/
  • 范围: stellar-core、rs-soroban-sdk、rs-soroban-env、soroban-tools(CLI + RPC)、js-soroban-client、rs-stellar-xdr、wasmi fork
  • 奖励: 严重$50K–$250K,高$10K–$50K,中$5K,低$1K
  • 支付: 以USD计价,用XLM支付。需要KYC。
  • 规则: 需要PoC。仅在本地分叉测试(禁止主网/测试网)。

Immunefi — OpenZeppelin on Stellar (up to $25K)

Immunefi — OpenZeppelin on Stellar(最高$25K)

HackerOne — Web Applications

HackerOne — Web应用

Soroban Audit Bank

Soroban审计库

SDF's proactive security program with $3M+ deployed across 43+ audits.
  • URL: https://stellar.org/grants-and-funding/soroban-audit-bank
  • Projects list: https://stellar.org/audit-bank/projects
  • Eligibility: SCF-funded projects (financial protocols, infrastructure, high-traction dApps)
  • Co-payment: 5% upfront (refundable if Critical/High/Medium issues remediated within 20 business days)
  • Follow-up audits: Triggered at $10M and $100M TVL milestones (includes formal verification and competitive audits)
  • Preparation: STRIDE threat modeling framework + Audit Readiness Checklist
SDF的主动安全计划,已部署$3M+资金完成43+次审计

Partner Audit Firms

合作审计公司

FirmSpecialty
OtterSecSmart contract audits
VeridiseTool-assisted audits, security checklist
Runtime VerificationFormal methods, Komet tool
CoinFabrikStatic analysis (Scout), manual audits
QuarksLabSecurity research
CoinspectSecurity audits
CertoraFormal verification (Sunbeam Prover)
HalbornSecurity assessments
ZellicBlockchain + cryptography research
Code4renaCompetitive audit platform
公司专业领域
OtterSec智能合约审计
Veridise工具辅助审计,安全检查清单
Runtime Verification形式化方法,Komet工具
CoinFabrik静态分析(Scout)、手动审计
QuarksLab安全研究
Coinspect安全审计
Certora形式化验证(Sunbeam Prover
Halborn安全评估
Zellic区块链+密码学研究
Code4rena竞争性审计平台

Security Tooling

安全工具

Static Analysis

静态分析

Scout Soroban (CoinFabrik)

Scout Soroban(CoinFabrik)

Open-source vulnerability detector with 23 detectors (critical through enhancement severity).
  • GitHub: https://github.com/CoinFabrik/scout-soroban
  • Install:
    cargo install cargo-scout-audit
    cargo scout-audit
  • Output formats: HTML, Markdown, JSON, PDF, SARIF (CI/CD integration)
  • VSCode extension: Scout Audit
  • Key detectors:
    overflow-check
    ,
    unprotected-update-current-contract-wasm
    ,
    set-contract-storage
    ,
    unrestricted-transfer-from
    ,
    divide-before-multiply
    ,
    dos-unbounded-operation
    ,
    unsafe-unwrap
开源漏洞检测器,包含23个检测器(从严重到增强级别)。
  • GitHub: https://github.com/CoinFabrik/scout-soroban
  • 安装:
    cargo install cargo-scout-audit
    cargo scout-audit
  • 输出格式: HTML、Markdown、JSON、PDF、SARIF(CI/CD集成)
  • VSCode扩展: Scout Audit
  • 关键检测器:
    overflow-check
    unprotected-update-current-contract-wasm
    set-contract-storage
    unrestricted-transfer-from
    divide-before-multiply
    dos-unbounded-operation
    unsafe-unwrap

OpenZeppelin Security Detectors SDK

OpenZeppelin安全检测器SDK

Framework for building custom security detectors for Soroban.
  • GitHub: https://github.com/OpenZeppelin/soroban-security-detectors-sdk
  • Architecture:
    sdk
    (core),
    detectors
    (pre-built),
    soroban-scanner
    (CLI)
  • Pre-built detectors:
    auth_missing
    ,
    unchecked_ft_transfer
    , improper TTL extension, contract panics, unsafe temporary storage
  • Extensible: Load external detector libraries as shared objects
用于为Soroban构建自定义安全检测器的框架。
  • GitHub: https://github.com/OpenZeppelin/soroban-security-detectors-sdk
  • 架构:
    sdk
    (核心)、
    detectors
    (预构建)、
    soroban-scanner
    (CLI)
  • 预构建检测器:
    auth_missing
    unchecked_ft_transfer
    、不当TTL延长、合约panic、不安全临时存储
  • 可扩展: 加载外部检测器库为共享对象

Formal Verification

形式化验证

Certora Sunbeam Prover

Certora Sunbeam Prover

Purpose-built formal verification for Soroban — first WASM platform supported by Certora.
专为Soroban设计的形式化验证工具——Certora支持的首个WASM平台。

Runtime Verification — Komet

Runtime Verification — Komet

Formal verification and testing tool built specifically for Soroban (SCF-funded).
专为Soroban构建的形式化验证和测试工具(SCF资助)。

Security Knowledge Base

安全知识库

Soroban Security Portal

Soroban安全门户

Community security knowledge base by Inferara (SCF-funded).
由Inferara维护的社区安全知识库(SCF资助)。

Security Monitoring (Post-Deployment)

安全监控(部署后)

OpenZeppelin Monitor (Stellar alpha)

OpenZeppelin Monitor(Stellar alpha版)

Open-source contract monitoring with Stellar support.
开源合约监控工具,支持Stellar。

OpenZeppelin Partnership Overview

OpenZeppelin合作概述

Strategic partnership highlights include:

战略合作亮点包括:

Part 4: Advanced Patterns

第四部分:高级模式

When to use this guide

适用场景

Use this guide for higher-complexity contract architecture:
  • Upgrades and migrations
  • Factory/deployer systems
  • Governance and timelocks
  • DeFi primitives (vaults, pools, oracles)
  • Regulated token/compliance workflows
  • Resource and storage optimization
For core contract syntax and day-to-day patterns, refer to the earlier sections in this guide covering contract structure, storage, authorization, cross-contract calls, events, error handling, and testing.
本指南适用于更高复杂度的合约架构:
  • 升级与迁移
  • 工厂/部署器系统
  • 治理与时间锁
  • DeFi原语(金库、池、预言机)
  • 受监管代币/合规流程
  • 资源与存储优化
核心合约语法和日常模式,请参阅本指南前面涵盖合约结构、存储、授权、跨合约调用、事件、错误处理和测试的章节。

Design principles

设计原则

  • Prefer simple state machines over implicit behavior.
  • Minimize privileged entrypoints and protect all privileged actions with explicit auth.
  • Keep upgrades predictable: version metadata + migration plan + rollback strategy.
  • Use idempotent migrations and fail fast on incompatible versions.
  • Separate protocol/business logic from governance/admin logic when possible.
  • 优先选择简单状态机而非隐式行为。
  • 最小化特权入口点,并用显式授权保护所有特权操作。
  • 保持升级可预测:版本元数据 + 迁移计划 + 回滚策略。
  • 使用幂等迁移,对不兼容版本快速失败。
  • 尽可能将协议/业务逻辑与治理/管理员逻辑分离。

Upgradeability patterns

可升级模式

1) Explicit upgrade policy

1) 显式升级策略

  • Decide early whether the contract is mutable or immutable.
  • If mutable, implement an
    upgrade
    entrypoint guarded by admin or governance.
  • If immutable, do not expose upgrade capability.
  • 尽早决定合约是可变还是不可变。
  • 若可变,实现由管理员或治理保护的
    upgrade
    入口点。
  • 若不可变,不暴露升级能力。

2) Version tracking

2) 版本跟踪

Track both runtime and code version:
  • Contract metadata (
    contractmeta!
    ) for binary version
  • Storage key for migration/application version
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contractmeta, contracttype, Address, BytesN, Env};

contractmeta!(key = "binver", val = "1.0.0");

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    AppVersion,
}

#[contract]
pub struct Upgradeable;

#[contractimpl]
impl Upgradeable {
    pub fn __constructor(env: Env, admin: Address) {
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::AppVersion, &1u32);
    }

    pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
        let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
        admin.require_auth();
        env.deployer().update_current_contract_wasm(new_wasm_hash);
    }
}
跟踪运行时和代码版本:
  • 二进制版本使用合约元数据(
    contractmeta!
  • 迁移/应用版本使用存储键
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, contractmeta, contracttype, Address, BytesN, Env};

contractmeta!(key = "binver", val = "1.0.0");

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
    Admin,
    AppVersion,
}

#[contract]
pub struct Upgradeable;

#[contractimpl]
impl Upgradeable {
    pub fn __constructor(env: Env, admin: Address) {
        env.storage().instance().set(&DataKey::Admin, &admin);
        env.storage().instance().set(&DataKey::AppVersion, &1u32);
    }

    pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
        let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
        admin.require_auth();
        env.deployer().update_current_contract_wasm(new_wasm_hash);
    }
}

3) Migration entrypoint

3) 迁移入口点

  • Add a dedicated
    migrate
    function after upgrades.
  • Ensure migration is monotonic (
    new_version > current_version
    ).
  • Treat migrations as one-way and idempotent.
  • 升级后添加专用的
    migrate
    函数。
  • 确保迁移是单调的(
    new_version > current_version
    )。
  • 将迁移视为单向且幂等的操作。

Factory and deployment patterns

工厂与部署模式

Factory contract responsibilities

工厂合约职责

  • Authorize who can deploy instances.
  • Derive deterministic addresses with salts when needed.
  • Emit events for deployments (indexing/ops observability).
  • Keep deployment logic separate from instance business logic.
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Val, Vec};

#[contract]
pub struct Factory;

#[contractimpl]
impl Factory {
    pub fn deploy(
        env: Env,
        owner: Address,
        wasm_hash: BytesN<32>,
        salt: BytesN<32>,
        constructor_args: Vec<Val>,
    ) -> Address {
        owner.require_auth();
        env.deployer()
            .with_address(env.current_contract_address(), salt)
            .deploy_v2(wasm_hash, constructor_args)
    }
}
Operational note:
  • Keep a registry (or emit canonical deployment events) to avoid orphaned instances.
  • 授权谁可以部署实例。
  • 需要时使用salt派生确定性地址。
  • 为部署触发事件(索引/运维可观测性)。
  • 将部署逻辑与实例业务逻辑分离。
rust
#![no_std]
use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Val, Vec};

#[contract]
pub struct Factory;

#[contractimpl]
impl Factory {
    pub fn deploy(
        env: Env,
        owner: Address,
        wasm_hash: BytesN<32>,
        salt: BytesN<32>,
        constructor_args: Vec<Val>,
    ) -> Address {
        owner.require_auth();
        env.deployer()
            .with_address(env.current_contract_address(), salt)
            .deploy_v2(wasm_hash, constructor_args)
    }
}
操作注意事项:
  • 维护注册表(或触发规范部署事件)以避免孤立实例。

Governance patterns

治理模式

Timelock for sensitive actions

敏感操作时间锁

Use a timelock for upgrades and major config changes:
  • propose_*
    stores pending action + execute ledger
  • execute_*
    enforces delay
  • cancel_*
    allows governance abort
对升级和主要配置变更使用时间锁:
  • propose_*
    存储待处理操作 + 执行账本高度
  • execute_*
    强制执行延迟
  • cancel_*
    允许治理中止操作

Multisig and role separation

多签与角色分离

  • Separate roles: proposer, approver, executor.
  • Define threshold and signer rotation process.
  • Record proposal state in persistent storage and prevent replay.
Checklist:
  • Proposal uniqueness and replay protection
  • Expiry semantics
  • Clear cancellation path
  • Explicit event emission
  • 分离角色:提案者、批准者、执行者。
  • 定义阈值和签名者轮换流程。
  • 在持久存储中记录提案状态并防止重放。
检查清单:
  • 提案唯一性和重放保护
  • 过期语义
  • 清晰的取消路径
  • 显式事件触发

DeFi primitives

DeFi原语

Vaults

金库

  • Track
    total_assets
    and
    total_shares
    with careful rounding rules.
  • Use conservative math for mint/redeem conversions.
  • Enforce pause/emergency controls for admin-level intervention.
  • 使用谨慎的舍入规则跟踪
    total_assets
    total_shares
  • 对铸币/赎回转换使用保守数学。
  • 为管理员级干预强制执行暂停/紧急控制。

Pools/AMMs

池/AMM

  • Define invariant and fee accounting precisely.
  • Protect against stale pricing and manipulation.
  • Include slippage checks on all user-facing swaps.
  • 精确定义不变量和费用核算。
  • 防止过时定价和操纵。
  • 在所有用户面对的交换中包含滑点检查。

Oracle integration

预言机集成

  • Require freshness constraints (ledger/time bounds).
  • Prefer median/multi-source feeds for critical operations.
  • Add circuit breakers for extreme price movement.
  • 要求新鲜度约束(账本/时间范围)。
  • 关键操作优先选择中位数/多源数据源。
  • 为极端价格波动添加断路器。

Compliance-oriented token design

合规导向的代币设计

Common regulated features:
  • Allowlist/denylist checks before transfer
  • Jurisdiction or investor-class restrictions
  • Forced transfer/freeze authority with auditable governance
  • Off-chain identity references (never store sensitive PII directly)
Implementation guidance:
  • Keep compliance policy in dedicated modules/entrypoints.
  • Emit policy decision events for traceability.
  • Treat privileged compliance actions as high-risk operations requiring strong auth.
常见受监管特性:
  • 转账前的允许列表/拒绝列表检查
  • 司法管辖区或投资者类别限制
  • 带可审计治理的强制转账/冻结权限
  • 链下身份引用(绝不直接存储敏感PII)
实现指南:
  • 将合规策略放在专用模块/入口点。
  • 为可追溯性触发策略决策事件。
  • 将特权合规操作视为高风险操作,需要强授权。

Resource optimization

资源优化

Storage

存储

  • Use
    instance
    for global config.
  • Use
    persistent
    for critical user state.
  • Use
    temporary
    only for disposable data.
  • Extend TTL strategically, not on every call.
  • 使用
    instance
    存储全局配置。
  • 使用
    persistent
    存储关键用户状态。
  • 使用
    temporary
    仅存储一次性数据。
  • 战略性延长TTL,而非每次调用都延长。

Compute

计算

  • Avoid unbounded loops over user-controlled collections.
  • Prefer bounded batch operations.
  • Reduce cross-contract calls in hot paths.
  • 避免对用户控制的集合进行无界循环。
  • 优先选择有界批量操作。
  • 减少热路径中的跨合约调用。

Contract size

合约大小

  • Keep release profile optimized (
    opt-level = "z"
    ,
    lto = true
    ,
    panic = "abort"
    ).
  • Split concerns across contracts when near Wasm size limits.
  • 保持release配置优化(
    opt-level = "z"
    lto = true
    panic = "abort"
    )。
  • 当接近Wasm大小限制时,将关注点拆分到多个合约。

Security review checklist for advanced architectures

高级架构安全审查检查清单

  • Access control is explicit on every privileged path.
  • Upgrade and migration are both tested (happy path + failure path).
  • Timelock and governance logic is replay-safe.
  • External dependency assumptions are documented.
  • Emergency controls and incident runbooks are defined.
  • Events cover operationally important transitions.
  • 每个特权路径的访问控制都是显式的。
  • 升级和迁移都经过测试(正常路径 + 失败路径)。
  • 时间锁和治理逻辑防重放。
  • 外部依赖假设已文档化。
  • 紧急控制和事件运行手册已定义。
  • 事件覆盖运维重要的转换。

Testing strategy for advanced patterns

高级模式测试策略

  • Unit tests for role checks, invariants, and edge-case math.
  • Integration tests for multi-step governance flows.
  • Upgrade tests from old state snapshots to new versions.
  • Negative tests for unauthorized and malformed calls.

  • 角色检查、不变量和边缘情况数学的单元测试。
  • 多步骤治理流程的集成测试。
  • 从旧状态快照到新版本的升级测试。
  • 未授权和格式错误调用的负面测试。

Part 5: Common Pitfalls

第五部分:常见陷阱

Soroban Contract Issues

Soroban合约问题

1. Contract Size Exceeds 64KB Limit

1. 合约大小超过64KB限制

Problem: Contract won't deploy because WASM exceeds size limit.
Symptoms:
Error: contract exceeds maximum size
Solutions:
toml
undefined
问题:合约无法部署,因为WASM超过大小限制。
症状:
Error: contract exceeds maximum size
解决方案:
toml
undefined

Cargo.toml - Use aggressive optimization

Cargo.toml - 使用激进优化

[profile.release] opt-level = "z" # Optimize for size lto = true # Link-time optimization codegen-units = 1 # Single codegen unit panic = "abort" # Smaller panic handling strip = "symbols" # Remove symbols

Additional strategies:
- Split large contracts into multiple smaller contracts
- Use `symbol_short!()` for symbols under 9 chars
- Avoid large static data in contract
- Remove debug code and unused functions
- Use `cargo bloat` to identify large dependencies

```bash
[profile.release] opt-level = "z" # 优化大小 lto = true # 链接时优化 codegen-units = 1 # 单代码生成单元 panic = "abort" # 更小的panic处理 strip = "symbols" # 移除符号

其他策略:
- 将大型合约拆分为多个较小合约
- 对9字符以下的符号使用`symbol_short!()`
- 避免合约中包含大型静态数据
- 移除调试代码和未使用函数
- 使用`cargo bloat`识别大型依赖

```bash

Check contract size

检查合约大小

ls -la target/wasm32-unknown-unknown/release/*.wasm
ls -la target/wasm32-unknown-unknown/release/*.wasm

Analyze what's taking space

分析占用空间的内容

cargo install cargo-bloat cargo bloat --release --target wasm32-unknown-unknown

---
cargo install cargo-bloat cargo bloat --release --target wasm32-unknown-unknown

---

2.
#![no_std]
Missing

2. 缺失
#![no_std]

Problem: Compilation fails with std library errors.
Symptoms:
error: cannot find macro `println` in this scope
error[E0433]: failed to resolve: use of undeclared crate or module `std`
Solution:
rust
// MUST be first line of lib.rs
#![no_std]

use soroban_sdk::{contract, contractimpl, Env};

// Use soroban_sdk equivalents instead of std:
// - soroban_sdk::String instead of std::string::String
// - soroban_sdk::Vec instead of std::vec::Vec
// - soroban_sdk::Map instead of std::collections::HashMap

问题:编译失败,出现标准库错误。
症状:
error: cannot find macro `println` in this scope
error[E0433]: failed to resolve: use of undeclared crate or module `std`
解决方案:
rust
// 必须是lib.rs的第一行
#![no_std]

use soroban_sdk::{contract, contractimpl, Env};

// 使用soroban_sdk等价物替代std:
// - soroban_sdk::String替代std::string::String
// - soroban_sdk::Vec替代std::vec::Vec
// - soroban_sdk::Map替代std::collections::HashMap

3. Storage TTL Not Extended

3. 未延长存储TTL

Problem: Contract data gets archived and becomes inaccessible.
Symptoms:
  • Contract calls fail after period of inactivity
  • Data appears missing but contract still exists
Solution:
rust
// Proactively extend TTL in operations that use data
pub fn use_data(env: Env) {
    // Extend instance storage
    env.storage().instance().extend_ttl(
        50,      // If TTL < 50, extend
        518400,  // Extend to ~30 days
    );

    // Extend specific persistent keys
    env.storage().persistent().extend_ttl(
        &DataKey::ImportantData,
        50,
        518400,
    );

    // Now use the data...
}
See Part 1: Contract Development above for full TTL management patterns and storage type guidance.

问题:合约数据被归档并变得不可访问。
症状:
  • 合约调用在一段时间不活动后失败
  • 数据看似缺失但合约仍存在
解决方案:
rust
// 在使用数据的操作中主动延长TTL
pub fn use_data(env: Env) {
    // 延长实例存储
    env.storage().instance().extend_ttl(
        50,      // 若TTL < 50,则延长
        518400,  // 延长至约30天
    );

    // 延长特定持久键
    env.storage().persistent().extend_ttl(
        &DataKey::ImportantData,
        50,
        518400,
    );

    // 现在使用数据...
}
有关完整的TTL管理模式和存储类型指南,请参阅第一部分:合约开发。

4. Wrong Storage Type

4. 错误的存储类型

Problem: Data unexpectedly deleted or costs too high.
Symptoms:
  • Temporary data deleted before expected
  • Unexpectedly high fees for storage
Solution:
rust
// Instance: Shared config, survives with contract
env.storage().instance().set(&DataKey::Admin, &admin);

// Persistent: User data, can be archived but restored
env.storage().persistent().set(&DataKey::Balance(user), &balance);

// Temporary: Truly temporary, auto-deleted, cheapest
env.storage().temporary().set(&DataKey::Cache(key), &value);

问题:数据意外删除或成本过高。
症状:
  • 临时数据在预期前被删除
  • 存储费用意外高昂
解决方案:
rust
// Instance: 共享配置,随合约存活
env.storage().instance().set(&DataKey::Admin, &admin);

// Persistent: 用户数据,可归档但可恢复
env.storage().persistent().set(&DataKey::Balance(user), &balance);

// Temporary: 真正临时的数据,自动删除,成本最低
env.storage().temporary().set(&DataKey::Cache(key), &value);

5. Authorization Not Working

5. 授权不生效

Problem:
require_auth()
not enforcing signatures in tests.
Symptoms:
  • Tests pass but transactions fail on network
  • Anyone can call protected functions
Solution:
rust
#[test]
fn test_auth() {
    let env = Env::default();

    // DON'T just mock all auths blindly
    // env.mock_all_auths();  // Be careful with this!

    // DO test specific auth requirements with mock_auths()
    env.mock_auths(&[MockAuth {
        address: &user,
        invoke: &MockAuthInvoke {
            contract: &contract_id,
            fn_name: "transfer",
            args: (&user, &other, &100i128).into_val(&env),
            sub_invokes: &[],
        },
    }]);

    client.transfer(&user, &other, &100);
    assert!(!env.auths().is_empty());
}
See Part 2: Testing Strategy above for comprehensive auth testing patterns including
mock_all_auths()
, specific auth mocking, and cross-contract auth.

问题
require_auth()
在测试中未强制执行签名。
症状:
  • 测试通过但交易在网络上失败
  • 任何人都可以调用受保护的函数
解决方案:
rust
#[test]
fn test_auth() {
    let env = Env::default();

    // 不要盲目模拟所有授权
    // env.mock_all_auths();  // 使用此功能要谨慎!

    // 使用mock_auths()测试特定授权要求
    env.mock_auths(&[MockAuth {
        address: &user,
        invoke: &MockAuthInvoke {
            contract: &contract_id,
            fn_name: "transfer",
            args: (&user, &other, &100i128).into_val(&env),
            sub_invokes: &[],
        },
    }]);

    client.transfer(&user, &other, &100);
    assert!(!env.auths().is_empty());
}
有关全面的授权测试模式,包括
mock_all_auths()
、特定授权模拟和跨合约授权,请参阅第二部分:测试策略。

SDK Issues

SDK问题

6. Network Passphrase Mismatch

6. 网络密码不匹配

Problem: Transactions fail with signature errors.
Symptoms:
Error: tx_bad_auth
Solution:
typescript
import * as StellarSdk from "@stellar/stellar-sdk";

// ALWAYS use correct passphrase for network
const PASSPHRASES = {
  mainnet: StellarSdk.Networks.PUBLIC,
  // "Public Global Stellar Network ; September 2015"

  testnet: StellarSdk.Networks.TESTNET,
  // "Test SDF Network ; September 2015"

  local: "Standalone Network ; February 2017",
};

// When building transactions
const tx = new StellarSdk.TransactionBuilder(account, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase: PASSPHRASES.testnet, // Match your network!
});

问题:交易因签名错误失败。
症状:
Error: tx_bad_auth
解决方案:
typescript
import * as StellarSdk from "@stellar/stellar-sdk";

// 始终为网络使用正确的密码
const PASSPHRASES = {
  mainnet: StellarSdk.Networks.PUBLIC,
  // "Public Global Stellar Network ; September 2015"

  testnet: StellarSdk.Networks.TESTNET,
  // "Test SDF Network ; September 2015"

  local: "Standalone Network ; February 2017",
};

// 构建交易时
const tx = new StellarSdk.TransactionBuilder(account, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase: PASSPHRASES.testnet, // 匹配你的网络!
});

7. Account Not Funded

7. 账户未资助

Problem: Operations fail because account doesn't exist.
Symptoms:
Error: Account not found
Status: 404
Solution:
typescript
// Testnet - use Friendbot
await fetch(`https://friendbot.stellar.org?addr=${publicKey}`);

// Mainnet - must receive XLM from existing account
const tx = new StellarSdk.TransactionBuilder(funderAccount, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase: StellarSdk.Networks.PUBLIC,
})
  .addOperation(
    StellarSdk.Operation.createAccount({
      destination: newAccountPublicKey,
      startingBalance: "2", // Minimum ~1 XLM for base reserve
    })
  )
  .setTimeout(180)
  .build();

问题:操作因账户不存在失败。
症状:
Error: Account not found
Status: 404
解决方案:
typescript
// 测试网 - 使用Friendbot
await fetch(`https://friendbot.stellar.org?addr=${publicKey}`);

// 主网 - 必须从现有账户接收XLM
const tx = new StellarSdk.TransactionBuilder(funderAccount, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase: StellarSdk.Networks.PUBLIC,
})
  .addOperation(
    StellarSdk.Operation.createAccount({
      destination: newAccountPublicKey,
      startingBalance: "2", // 基础储备最低约1 XLM
    })
  )
  .setTimeout(180)
  .build();

8. Missing Trustline

8. 缺失信任线

Problem: Payment fails for non-native assets.
Symptoms:
Error: op_no_trust
Solution:
typescript
// Check if trustline exists
const account = await server.loadAccount(destination);
const hasTrustline = account.balances.some(
  (b) =>
    b.asset_type !== "native" &&
    b.asset_code === asset.code &&
    b.asset_issuer === asset.issuer
);

if (!hasTrustline) {
  // Create trustline first
  const trustTx = new StellarSdk.TransactionBuilder(destAccount, {
    fee: StellarSdk.BASE_FEE,
    networkPassphrase,
  })
    .addOperation(StellarSdk.Operation.changeTrust({ asset }))
    .setTimeout(180)
    .build();
  // Sign and submit...
}

问题:非原生资产支付失败。
症状:
Error: op_no_trust
解决方案:
typescript
// 检查信任线是否存在
const account = await server.loadAccount(destination);
const hasTrustline = account.balances.some(
  (b) =>
    b.asset_type !== "native" &&
    b.asset_code === asset.code &&
    b.asset_issuer === asset.issuer
);

if (!hasTrustline) {
  // 先创建信任线
  const trustTx = new StellarSdk.TransactionBuilder(destAccount, {
    fee: StellarSdk.BASE_FEE,
    networkPassphrase,
  })
    .addOperation(StellarSdk.Operation.changeTrust({ asset }))
    .setTimeout(180)
    .build();
  // 签名并提交...
}

9. Sequence Number Issues

9. 序列号问题

Problem: Transaction rejected for sequence number.
Symptoms:
Error: tx_bad_seq
Causes & Solutions:
typescript
// Cause 1: Stale account data
// Solution: Always load fresh account before building tx
const account = await server.loadAccount(publicKey);

// Cause 2: Parallel transactions
// Solution: Use sequence number management
class SequenceManager {
  private sequence: bigint;

  async getNext(server: Horizon.Server, publicKey: string) {
    if (!this.sequence) {
      const account = await server.loadAccount(publicKey);
      this.sequence = BigInt(account.sequence);
    }
    this.sequence++;
    return this.sequence.toString();
  }
}

// Cause 3: Transaction timeout without resubmit
// Solution: Rebuild with fresh sequence on timeout

问题:交易因序列号被拒绝。
症状:
Error: tx_bad_seq
原因与解决方案:
typescript
// 原因1: 账户数据过时
// 解决方案: 构建交易前始终加载最新账户
const account = await server.loadAccount(publicKey);

// 原因2: 并行交易
// 解决方案: 使用序列号管理
class SequenceManager {
  private sequence: bigint;

  async getNext(server: Horizon.Server, publicKey: string) {
    if (!this.sequence) {
      const account = await server.loadAccount(publicKey);
      this.sequence = BigInt(account.sequence);
    }
    this.sequence++;
    return this.sequence.toString();
  }
}

// 原因3: 交易超时未重新提交
// 解决方案: 超时后使用最新序列重建

10. Soroban Transaction Not Simulated

10. Soroban交易未模拟

Problem: Soroban transaction fails with resource errors.
Symptoms:
Error: transaction simulation failed
Error: insufficient resources
Solution:
typescript
// ALWAYS simulate before submitting Soroban transactions
const simulation = await rpc.simulateTransaction(transaction);

if (StellarSdk.rpc.Api.isSimulationError(simulation)) {
  throw new Error(`Simulation failed: ${simulation.error}`);
}

// Use assembleTransaction to add correct resources
const preparedTx = StellarSdk.rpc.assembleTransaction(
  transaction,
  simulation
).build();

// Now sign and submit preparedTx, not original transaction

问题:Soroban交易因资源错误失败。
症状:
Error: transaction simulation failed
Error: insufficient resources
解决方案:
typescript
// 提交Soroban交易前始终模拟
const simulation = await rpc.simulateTransaction(transaction);

if (StellarSdk.rpc.Api.isSimulationError(simulation)) {
  throw new Error(`Simulation failed: ${simulation.error}`);
}

// 使用assembleTransaction添加正确资源
const preparedTx = StellarSdk.rpc.assembleTransaction(
  transaction,
  simulation
).build();

// 现在签名并提交preparedTx,而非原始交易

Frontend Issues

前端问题

11. Freighter Not Detected

11. Freighter未被检测到

Problem: Wallet connection fails silently.
Symptoms:
  • isConnected()
    returns false
  • No wallet prompt appears
Solution:
typescript
import { isConnected, isAllowed } from "@stellar/freighter-api";

async function checkFreighter() {
  // Check if extension is installed
  const connected = await isConnected();
  if (!connected) {
    // Prompt user to install
    window.open("https://freighter.app", "_blank");
    return;
  }

  // Check if app is allowed
  const allowed = await isAllowed();
  if (!allowed) {
    // Need to request permission
    await setAllowed();
  }
}

问题:钱包连接静默失败。
症状:
  • isConnected()
    返回false
  • 无钱包提示出现
解决方案:
typescript
import { isConnected, isAllowed } from "@stellar/freighter-api";

async function checkFreighter() {
  // 检查扩展是否安装
  const connected = await isConnected();
  if (!connected) {
    // 提示用户安装
    window.open("https://freighter.app", "_blank");
    return;
  }

  // 检查应用是否被允许
  const allowed = await isAllowed();
  if (!allowed) {
    // 需要请求权限
    await setAllowed();
  }
}

12. Network Mismatch with Wallet

12. 钱包与应用网络不匹配

Problem: Wallet on different network than app.
Symptoms:
  • Transactions fail unexpectedly
  • Wrong balances displayed
Solution:
typescript
import { getNetwork } from "@stellar/freighter-api";

async function validateNetwork() {
  const walletNetwork = await getNetwork();
  const appNetwork = process.env.NEXT_PUBLIC_STELLAR_NETWORK;

  if (walletNetwork !== appNetwork) {
    throw new Error(
      `Please switch Freighter to ${appNetwork}. Currently on ${walletNetwork}`
    );
  }
}

问题:钱包与应用处于不同网络。
症状:
  • 交易意外失败
  • 显示错误余额
解决方案:
typescript
import { getNetwork } from "@stellar/freighter-api";

async function validateNetwork() {
  const walletNetwork = await getNetwork();
  const appNetwork = process.env.NEXT_PUBLIC_STELLAR_NETWORK;

  if (walletNetwork !== appNetwork) {
    throw new Error(
      `请将Freighter切换到${appNetwork}网络。当前为${walletNetwork}`
    );
  }
}

13. Transaction Timeout

13. 交易超时

Problem: Transaction expires before confirmation.
Symptoms:
Error: tx_too_late
Solution:
typescript
// Set appropriate timeout based on expected confirmation time
const tx = new StellarSdk.TransactionBuilder(account, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase,
})
  .addOperation(/* ... */)
  .setTimeout(180) // 3 minutes - adjust as needed
  .build();

// Handle timeout gracefully
async function submitWithRetry(signedXdr: string) {
  try {
    return await submitTransaction(signedXdr);
  } catch (error) {
    if (error.response?.data?.extras?.result_codes?.transaction === "tx_too_late") {
      // Rebuild with fresh blockhash and retry
      const newTx = await rebuildTransaction(signedXdr);
      return await submitTransaction(newTx);
    }
    throw error;
  }
}

问题:交易在确认前过期。
症状:
Error: tx_too_late
解决方案:
typescript
// 根据预期确认时间设置适当超时
const tx = new StellarSdk.TransactionBuilder(account, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase,
})
  .addOperation(/* ... */)
  .setTimeout(180) // 3分钟 - 根据需要调整
  .build();

// 优雅处理超时
async function submitWithRetry(signedXdr: string) {
  try {
    return await submitTransaction(signedXdr);
  } catch (error) {
    if (error.response?.data?.extras?.result_codes?.transaction === "tx_too_late") {
      // 使用最新区块哈希重建并重试
      const newTx = await rebuildTransaction(signedXdr);
      return await submitTransaction(newTx);
    }
    throw error;
  }
}

CLI Issues

CLI问题

14. Identity Not Found

14. 身份未找到

Problem: Stellar CLI can't find saved identity.
Symptoms:
Error: identity "alice" not found
Solution:
bash
undefined
问题:Stellar CLI无法找到已保存的身份。
症状:
Error: identity "alice" not found
解决方案:
bash
undefined

List existing identities

列出现有身份

stellar keys list
stellar keys list

Generate new identity

生成新身份

stellar keys generate --global alice
stellar keys generate --global alice

For testnet with funding

带资助的测试网身份

stellar keys generate --global alice --network testnet --fund
stellar keys generate --global alice --network testnet --fund

Specify identity location

指定身份位置

stellar keys generate alice --config-dir /custom/path

---
stellar keys generate alice --config-dir /custom/path

---

15. Contract Invoke Parsing Errors

15. 合约调用解析错误

Problem: CLI can't parse function arguments.
Symptoms:
Error: invalid argument format
Solution:
bash
undefined
问题:CLI无法解析函数参数。
症状:
Error: invalid argument format
解决方案:
bash
undefined

Use correct argument syntax

使用正确的参数语法

Addresses: just the G... or C... string

地址:直接使用G...或C...字符串

stellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--
transfer
--from GABC...
--to GDEF...
--amount 1000
stellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--
transfer
--from GABC...
--to GDEF...
--amount 1000

Complex types: use JSON

复杂类型:使用JSON

stellar contract invoke
--id CONTRACT_ID
--
complex_fn
--data '{"field1": "value", "field2": 123}'

---
stellar contract invoke
--id CONTRACT_ID
--
complex_fn
--data '{"field1": "value", "field2": 123}'

---

General Best Practices

通用最佳实践

Debugging Checklist

调试检查清单

  1. Check network: Is wallet/CLI on correct network?
  2. Check account: Is source account funded?
  3. Check sequence: Is sequence number current?
  4. Check simulation: Did Soroban tx simulate successfully?
  5. Check trustlines: For asset transfers, do trustlines exist?
  6. Check TTL: For contract data, is TTL sufficient?
  7. Check authorization: Is correct account signing?
  8. Check logs: What does the error message actually say?
  1. 检查网络:钱包/CLI是否在正确网络?
  2. 检查账户:源账户是否已资助?
  3. 检查序列:序列号是否为最新?
  4. 检查模拟:Soroban交易是否模拟成功?
  5. 检查信任线:资产转账是否存在信任线?
  6. 检查TTL:合约数据的TTL是否足够?
  7. 检查授权:是否为正确账户签名?
  8. 检查日志:错误消息实际内容是什么?

Error Code Reference

错误代码参考

CodeMeaningCommon Fix
tx_bad_auth
Signature invalidCheck network passphrase
tx_bad_seq
Wrong sequenceReload account
tx_too_late
Transaction expiredRebuild and resubmit
op_no_trust
Missing trustlineCreate trustline first
op_underfunded
Insufficient balanceAdd funds
op_low_reserve
Below minimum balanceMaintain reserve
代码含义常见修复
tx_bad_auth
签名无效检查网络密码
tx_bad_seq
序列错误重新加载账户
tx_too_late
交易过期重建并重新提交
op_no_trust
缺失信任线先创建信任线
op_underfunded
余额不足添加资金
op_low_reserve
低于最低余额维持储备金