soroban
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSoroban 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
快速导航
- Initialization and constructors: Project Setup, Contract Constructors (Protocol 22+)
- Core implementation patterns: Core Contract Structure, Storage Types, Authorization
- Advanced interactions: Cross-Contract Calls, Events, Error Handling
- Delivery workflow: Building and Deploying, Unit Testing, Best Practices
- ZK status guidance: Zero-Knowledge Cryptography (Status-Sensitive)
Alternative Languages
替代语言
Rust is the primary and recommended language for Soroban contracts. Community-maintained alternatives exist but are not recommended for production:
- AssemblyScript: by Soneso — allows TypeScript-like syntax, officially listed on Stellar docs, but may lag behind the latest protocol version
as-soroban-sdk - Solidity: Hyperledger Solang — SDF-funded, compiles Solidity to Soroban WASM, currently pre-alpha (docs)
Rust是Soroban合约的首选推荐语言。社区维护的替代语言不建议用于生产环境:
- AssemblyScript:Soneso开发的—— 支持类TypeScript语法,已列入Stellar官方文档,但可能滞后于最新协议版本
as-soroban-sdk - Solidity:Hyperledger 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
关键约束
- required - no Rust standard library
#![no_std] - 64KB contract size limit (use release optimizations)
- Limited heap allocation
- No string type (use from soroban-sdk or
Stringfor short strings)Symbol - limited to 32 characters (was 10 in earlier versions)
Symbol
- 必须使用—— 不支持Rust标准库
#![no_std] - 合约大小限制为64KB(使用release优化)
- 堆分配受限
- 无原生字符串类型(使用soroban-sdk的或短字符串
String)Symbol - 最大长度为32字符(早期版本为10字符)
Symbol
Project Setup
项目搭建
Initialize a new contract
初始化新合约
bash
stellar contract init my-contract
cd my-contractThis creates:
my-contract/
├── Cargo.toml
├── src/
│ └── lib.rs
└── contracts/
└── hello_world/
├── Cargo.toml
└── src/
└── lib.rsbash
stellar contract init my-contract
cd my-contract生成的目录结构:
my-contract/
├── Cargo.toml
├── src/
│ └── lib.rs
└── contracts/
└── hello_world/
├── Cargo.toml
└── src/
└── lib.rsCargo.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 = truetoml
[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 = trueContract Constructors (Protocol 22+)
合约构造函数(协议22+)
Use constructors for atomic initialization when protocol support is available. This avoids a separate transaction and reduces front-running risk.
initialize当协议支持时,使用构造函数进行原子初始化。这避免了单独的交易,降低抢先交易风险。
initializeConstructor 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 100bash
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/my_contract.wasm \
--source alice \
--network testnet \
-- \
--admin alice \
--initial_value 100Rules
规则
- Name must be exactly.
__constructor - Constructor returns (no return value).
() - Runs only at creation time and does not run on upgrade.
- If constructor fails, deployment fails atomically.
- 名称必须严格为
__constructor - 构造函数返回(无返回值)
() - 仅在创建时运行,升级时不执行
- 若构造函数失败,部署会原子性失败
Backwards compatibility
向后兼容性
If targeting older protocol environments, use guarded patterns and prevent re-initialization explicitly.
initialize若针对旧版协议环境,使用受保护的模式,并明确防止重复初始化。
initializeCore 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
undefinedbash
undefinedBuild 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
undefinedundefinedDeploy to Testnet
部署到测试网
bash
undefinedbash
undefinedGenerate 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
--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
--wasm target/wasm32-unknown-unknown/release/my_contract.wasm
--source alice
--network testnet
Returns: CONTRACT_ID (starts with 'C')
返回结果: CONTRACT_ID(以'C'开头)
undefinedundefinedInitialize Contract
初始化合约
bash
stellar contract invoke \
--id CONTRACT_ID \
--source alice \
--network testnet \
-- \
initialize \
--admin alicebash
stellar contract invoke \
--id CONTRACT_ID \
--source alice \
--network testnet \
-- \
initialize \
--admin aliceInvoke Functions
调用合约函数
bash
stellar contract invoke \
--id CONTRACT_ID \
--source alice \
--network testnet \
-- \
incrementbash
stellar contract invoke \
--id CONTRACT_ID \
--source alice \
--network testnet \
-- \
incrementUnit 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 for symbols under 9 chars (more efficient)
symbol_short!() - 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 for sensitive operations
require_auth() - 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:
- CAP status in the CAP preamble (/
Acceptedvs draft/awaiting decision)Implemented - Target network software version and protocol support
- release support for the target host functions
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.
Part 2: Testing Strategy
第二部分:测试策略
Quick Navigation
快速导航
- Strategy overview: Testing Pyramid
- Core test layers: Unit Testing with Soroban SDK, Local Testing with Stellar Quickstart, Testnet Testing
- Integration and CI: Integration Testing Patterns, Test Configuration, CI/CD Configuration
- Advanced testing: Fuzz Testing, Property-Based Testing, Differential Testing with Test Snapshots, Fork Testing, Mutation Testing
- Performance and readiness: Resource Profiling, Best Practices
- 策略概述:测试金字塔
- 核心测试层:使用Soroban SDK进行单元测试、使用Stellar Quickstart进行本地测试、测试网测试
- 集成与CI:集成测试模式、测试配置、CI/CD配置
- 高级测试:模糊测试、属性测试、使用测试快照的差分测试、分叉测试、变异测试
- 性能与就绪性:资源分析、最佳实践
Testing Pyramid
测试金字塔
- Unit tests (fast): Native Rust tests with testutils
soroban-sdk - Local integration tests: Stellar Quickstart Docker
- Testnet tests: Deploy and test on public testnet
- Mainnet smoke tests: Final validation before production
- 单元测试(快速):使用测试工具的原生Rust测试
soroban-sdk - 本地集成测试:Stellar Quickstart Docker
- 测试网测试:在公开测试网部署并测试
- 主网冒烟测试:生产前最终验证
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
undefinedbash
undefinedPull and run Stellar Quickstart
拉取并运行Stellar Quickstart
docker run --rm -it -p 8000:8000
--name stellar
stellar/quickstart:latest
--local
--enable-soroban-rpc
--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
--name stellar
stellar/quickstart:latest
--local
--enable-soroban-rpc
Or use Stellar CLI
或使用Stellar CLI
stellar container start local
undefinedstellar container start local
undefinedConfigure 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
undefinedbash
undefinedUsing 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端点
curl "http://localhost:8000/friendbot?addr=G..."
undefinedcurl "http://localhost:8000/friendbot?addr=G..."
undefinedDeploy and Test Locally
本地部署与测试
bash
undefinedbash
undefinedDeploy contract to local network
将合约部署到本地网络
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source test-account
--network local
--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
--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
--id CONTRACT_ID
--source test-account
--network local
--
function_name
--arg value
undefinedstellar contract invoke
--id CONTRACT_ID
--source test-account
--network local
--
function_name
--arg value
--id CONTRACT_ID
--source test-account
--network local
--
function_name
--arg value
undefinedTestnet Testing
测试网测试
Network Configuration
网络配置
bash
undefinedbash
undefinedTestnet RPC: https://soroban-testnet.stellar.org
Testnet Horizon: https://horizon-testnet.stellar.org
测试网Horizon: https://horizon-testnet.stellar.org
Network Passphrase: "Test SDF Network ; September 2015"
网络密码: "Test SDF Network ; September 2015"
Friendbot: https://friendbot.stellar.org
Friendbot: https://friendbot.stellar.org
undefinedundefinedCreate and Fund Testnet Account
创建并资助测试网账户
bash
undefinedbash
undefinedGenerate 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
或手动操作
curl "https://friendbot.stellar.org?addr=G..."
undefinedcurl "https://friendbot.stellar.org?addr=G..."
undefinedDeploy to Testnet
部署到测试网
bash
undefinedbash
undefinedDeploy contract
部署合约
stellar contract deploy
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
--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
--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
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
undefinedstellar contract install
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
--wasm target/wasm32-unknown-unknown/release/contract.wasm
--source my-testnet-key
--network testnet
undefinedTestnet 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
Check reset schedule: 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 = truetoml
[dev-dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] } # 与[dependencies]版本匹配
[profile.test]
opt-level = 0
debug = trueRunning Tests
运行测试
bash
undefinedbash
undefinedRun 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
undefinedcargo test -- --ignored
undefinedCI/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 localyaml
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 localBest Practices
最佳实践
Test Organization
测试组织
project/
├── src/
│ └── lib.rs
├── tests/
│ ├── common/
│ │ └── mod.rs # Shared test utilities
│ ├── unit/
│ │ ├── mod.rs
│ │ └── transfer.rs
│ └── integration/
│ └── full_flow.rs
└── Cargo.tomlproject/
├── src/
│ └── lib.rs
├── tests/
│ ├── common/
│ │ └── mod.rs # 共享测试工具
│ ├── unit/
│ │ ├── mod.rs
│ │ └── transfer.rs
│ └── integration/
│ └── full_flow.rs
└── Cargo.tomlTest 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 and the built-in trait. All types automatically derive when the feature is active.
cargo-fuzzSorobanArbitrary#[contracttype]SorobanArbitrary"testutils"Soroban通过和内置的特性提供一流的模糊测试支持。当特性启用时,所有类型会自动派生。
cargo-fuzzSorobanArbitrary"testutils"#[contracttype]SorobanArbitrarySetup
设置
bash
undefinedbash
undefinedInstall 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.tomltoml
[dependencies]
soroban-sdk = { version = "25.0.1", features = ["testutils"] }cargo fuzz init
更新`Cargo.toml`以包含两种 crate 类型:
```toml
[lib]
crate-type = ["lib", "cdylib"]在中添加:
fuzz/Cargo.tomltoml
[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
undefinedbash
undefinedRun (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
undefinedcargo +nightly fuzz coverage fuzz_deposit
undefinedSoroban Token Fuzzer
Soroban代币模糊测试器
Reusable library for fuzzing token contracts:
可复用的代币合约模糊测试库:
Documentation
文档
Property-Based Testing
属性测试
Use with for QuickCheck-style property testing that runs in standard .
proptestSorobanArbitrarycargo testrust
#[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 interactively to find deep bugs, then convert to for regression prevention in CI.
cargo-fuzzproptest使用结合进行QuickCheck风格的属性测试,可在标准中运行。
proptestSorobanArbitrarycargo testrust
#[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);
}
}
}推荐工作流:使用交互式查找深层漏洞,然后转换为在CI中预防回归。
cargo-fuzzproptestDifferential Testing with Test Snapshots
使用测试快照的差分测试
Soroban automatically writes JSON snapshots at the end of every test to , capturing events and final ledger state. Commit these to source control — diffs reveal unintended behavioral changes.
test_snapshots/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
undefinedCreate 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);
}- Docs: Fork Testing
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 to verify test quality — modifies source code and checks that tests catch the changes.
cargo-mutantsbash
cargo install --locked cargo-mutants
cargo mutantsOutput interpretation:
-
CAUGHT: Tests detected the mutation (good coverage)
-
MISSED: Tests passed despite mutation (test gap — review)
mutants.out/diff/ -
Docs: Mutation Testing
使用验证测试质量——修改源代码并检查测试是否能捕获变化。
cargo-mutantsbash
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
undefinedbash
undefinedSimulate contract invocation to see resource costs
模拟合约调用查看资源成本
stellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
undefinedstellar contract invoke
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
--id CONTRACT_ID
--source alice
--network testnet
--sim-only
--
function_name --arg value
undefinedStellar 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 tablestypescript
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 equivalent. Contracts cannot execute arbitrary bytecode in their context, eliminating proxy-based attacks.
delegatecall与Ethereum不同,Soroban没有等价功能。合约无法在自身上下文中执行任意字节码,消除了代理攻击。
delegatecallNo 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 , making it explicit which operations need signatures.
require_auth()授权通过选择启用,明确哪些操作需要签名。
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 on the caller or an admin address. See Part 1: Contract Development above for full authorization patterns (direct auth, admin helpers, ).
require_auth()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_args2. 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
安全审查问题
- Can anyone call admin functions without authorization?
- Can the contract be reinitialized?
- Are all external contract calls validated?
- Is arithmetic safe from overflow/underflow?
- Can storage keys collide?
- Will critical data survive archival?
- Are cross-contract return values validated?
- Can timing attacks exploit state changes?
- 任何人无需授权即可调用管理员函数吗?
- 合约可被重新初始化吗?
- 所有外部合约调用都经过验证吗?
- 算术操作是否安全,无溢出/下溢?
- 存储键可能冲突吗?
- 关键数据能在归档后存活吗?
- 跨合约返回值已验证吗?
- 时间攻击能利用状态变化吗?
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)
- URL: https://immunefi.com/bug-bounty/openzeppelin-stellar/
- Scope: OpenZeppelin Stellar Contracts library
- Max payout: $25K per bug, $250K total program cap
- URL: https://immunefi.com/bug-bounty/openzeppelin-stellar/
- 范围: OpenZeppelin Stellar合约库
- 最高 payout: 每个漏洞$25K,计划总上限$250K
HackerOne — Web Applications
HackerOne — Web应用
- URL: https://stellar.org/grants-and-funding/bug-bounty
- Scope: SDF web applications, production servers, domains
- Disclosure: 90-day remediation window before public disclosure
- URL: https://stellar.org/grants-and-funding/bug-bounty
- 范围: SDF web应用、生产服务器、域名
- 披露: 公开披露前有90天修复窗口
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+次审计。
- URL: https://stellar.org/grants-and-funding/soroban-audit-bank
- 项目列表: https://stellar.org/audit-bank/projects
- 资格: SCF资助的项目(金融协议、基础设施、高流量dApp)
- 共付: 预付5%(若20个工作日内修复严重/高/中漏洞则退还)
- 后续审计: TVL达到$10M和$100M里程碑时触发(包括形式化验证和竞争性审计)
- 准备: STRIDE威胁建模框架 + 审计就绪检查清单
Partner Audit Firms
合作审计公司
| Firm | Specialty |
|---|---|
| OtterSec | Smart contract audits |
| Veridise | Tool-assisted audits, security checklist |
| Runtime Verification | Formal methods, Komet tool |
| CoinFabrik | Static analysis (Scout), manual audits |
| QuarksLab | Security research |
| Coinspect | Security audits |
| Certora | Formal verification (Sunbeam Prover) |
| Halborn | Security assessments |
| Zellic | Blockchain + cryptography research |
| Code4rena | Competitive 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-auditcargo 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-operationunsafe-unwrap
开源漏洞检测器,包含23个检测器(从严重到增强级别)。
- GitHub: https://github.com/CoinFabrik/scout-soroban
- 安装: →
cargo install cargo-scout-auditcargo 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-operationunsafe-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: (core),
sdk(pre-built),detectors(CLI)soroban-scanner - Pre-built detectors: ,
auth_missing, improper TTL extension, contract panics, unsafe temporary storageunchecked_ft_transfer - Extensible: Load external detector libraries as shared objects
用于为Soroban构建自定义安全检测器的框架。
- GitHub: https://github.com/OpenZeppelin/soroban-security-detectors-sdk
- 架构: (核心)、
sdk(预构建)、detectors(CLI)soroban-scanner - 预构建检测器: 、
auth_missing、不当TTL延长、合约panic、不安全临时存储unchecked_ft_transfer - 可扩展: 加载外部检测器库为共享对象
Formal Verification
形式化验证
Certora Sunbeam Prover
Certora Sunbeam Prover
Purpose-built formal verification for Soroban — first WASM platform supported by Certora.
- Docs: https://docs.certora.com/en/latest/docs/sunbeam/index.html
- Spec language: CVLR (Certora Verification Language for Rust) — Rust macros (,
cvlr_assert!,cvlr_assume!)cvlr_satisfy! - Operates at: WASM bytecode level (eliminates compiler trust assumptions)
- Reports: https://github.com/Certora/SecurityReports
- Example: Blend V1 verification report
专为Soroban设计的形式化验证工具——Certora支持的首个WASM平台。
- 文档: https://docs.certora.com/en/latest/docs/sunbeam/index.html
- 规范语言: CVLR(Certora Rust验证语言)——Rust宏(、
cvlr_assert!、cvlr_assume!)cvlr_satisfy! - 操作层级: WASM字节码层级(消除编译器信任假设)
- 报告: https://github.com/Certora/SecurityReports
- 示例: Blend V1验证报告
Runtime Verification — Komet
Runtime Verification — Komet
Formal verification and testing tool built specifically for Soroban (SCF-funded).
- Docs: https://docs.runtimeverification.com/komet
- Repo: https://github.com/runtimeverification/komet
- Spec language: Rust — property-based tests written in the same language as Soroban contracts
- Operates at: WASM bytecode level via KWasm semantics (eliminates compiler trust assumptions)
- Features: Fuzzing, testing, formal verification
- Reports: RV publications
- Example: TokenOps audit and verification report
- Blog: Introducing Komet
专为Soroban构建的形式化验证和测试工具(SCF资助)。
- 文档: https://docs.runtimeverification.com/komet
- 仓库: https://github.com/runtimeverification/komet
- 规范语言: Rust——用与Soroban合约相同的语言编写属性测试
- 操作层级: 通过KWasm语义在WASM字节码层级操作(消除编译器信任假设)
- 特性: 模糊测试、测试、形式化验证
- 报告: RV出版物
- 示例: TokenOps审计和验证报告
- 博客: Introducing Komet
Security Knowledge Base
安全知识库
Soroban Security Portal
Soroban安全门户
Community security knowledge base by Inferara (SCF-funded).
- URL: https://sorobansecurity.com
- Features: Searchable audit reports, vulnerability database, best practices
由Inferara维护的社区安全知识库(SCF资助)。
- URL: https://sorobansecurity.com
- 特性: 可搜索的审计报告、漏洞数据库、最佳实践
Security Monitoring (Post-Deployment)
安全监控(部署后)
OpenZeppelin Monitor (Stellar alpha)
OpenZeppelin Monitor(Stellar alpha版)
Open-source contract monitoring with Stellar support.
- Features: Self-hosted via Docker, Prometheus + Grafana observability
- Source: https://www.openzeppelin.com/news/monitor-and-relayers-are-now-open-source
开源合约监控工具,支持Stellar。
- 特性: 通过Docker自托管,Prometheus + Grafana可观测性
- 来源: https://www.openzeppelin.com/news/monitor-and-relayers-are-now-open-source
OpenZeppelin Partnership Overview
OpenZeppelin合作概述
Strategic partnership highlights include:
- 40 Auditor Weeks of dedicated security audits
- Stellar Contracts library (audited, production-ready)
- Relayer (fee-sponsored transactions, Stellar-native)
- Monitor (contract monitoring, Stellar alpha)
- Security Detectors SDK (static analysis framework)
- SEP authorship: SEP-0049 (Upgradeable Contracts), SEP-0050 (NFTs)
- Blog: https://stellar.org/blog/foundation-news/sdf-partners-with-openzeppelin-to-enhance-stellar-smart-contract-development
战略合作亮点包括:
- 40审计师周的专属安全审计
- Stellar合约库(已审计,生产就绪)
- Relayer(费用赞助交易,Stellar原生)
- Monitor(合约监控,Stellar alpha版)
- 安全检测器SDK(静态分析框架)
- SEP撰写: SEP-0049(可升级合约)、SEP-0050(NFT)
- 博客: https://stellar.org/blog/foundation-news/sdf-partners-with-openzeppelin-to-enhance-stellar-smart-contract-development
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 entrypoint guarded by admin or governance.
upgrade - If immutable, do not expose upgrade capability.
- 尽早决定合约是可变还是不可变。
- 若可变,实现由管理员或治理保护的入口点。
upgrade - 若不可变,不暴露升级能力。
2) Version tracking
2) 版本跟踪
Track both runtime and code version:
- Contract metadata () for binary version
contractmeta! - 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 function after upgrades.
migrate - 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:
- stores pending action + execute ledger
propose_* - enforces delay
execute_* - allows governance abort
cancel_*
对升级和主要配置变更使用时间锁:
- 存储待处理操作 + 执行账本高度
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 and
total_assetswith careful rounding rules.total_shares - 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 for global config.
instance - Use for critical user state.
persistent - Use only for disposable data.
temporary - 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 sizeSolutions:
toml
undefined问题:合约无法部署,因为WASM超过大小限制。
症状:
Error: contract exceeds maximum size解决方案:
toml
undefinedCargo.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`识别大型依赖
```bashCheck 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
#![no_std]2. 缺失#![no_std]
#![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::HashMap3. 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: not enforcing signatures in tests.
require_auth()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, specific auth mocking, and cross-contract auth.mock_all_auths()
问题:在测试中未强制执行签名。
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_authSolution:
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: 404Solution:
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_trustSolution:
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_seqCauses & 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 resourcesSolution:
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:
- returns false
isConnected() - 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();
}
}问题:钱包连接静默失败。
症状:
- 返回false
isConnected() - 无钱包提示出现
解决方案:
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_lateSolution:
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 foundSolution:
bash
undefined问题:Stellar CLI无法找到已保存的身份。
症状:
Error: identity "alice" not found解决方案:
bash
undefinedList 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 formatSolution:
bash
undefined问题:CLI无法解析函数参数。
症状:
Error: invalid argument format解决方案:
bash
undefinedUse 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
--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
--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}'
--id CONTRACT_ID
--
complex_fn
--data '{"field1": "value", "field2": 123}'
---stellar contract invoke
--id CONTRACT_ID
--
complex_fn
--data '{"field1": "value", "field2": 123}'
--id CONTRACT_ID
--
complex_fn
--data '{"field1": "value", "field2": 123}'
---General Best Practices
通用最佳实践
Debugging Checklist
调试检查清单
- Check network: Is wallet/CLI on correct network?
- Check account: Is source account funded?
- Check sequence: Is sequence number current?
- Check simulation: Did Soroban tx simulate successfully?
- Check trustlines: For asset transfers, do trustlines exist?
- Check TTL: For contract data, is TTL sufficient?
- Check authorization: Is correct account signing?
- Check logs: What does the error message actually say?
- 检查网络:钱包/CLI是否在正确网络?
- 检查账户:源账户是否已资助?
- 检查序列:序列号是否为最新?
- 检查模拟:Soroban交易是否模拟成功?
- 检查信任线:资产转账是否存在信任线?
- 检查TTL:合约数据的TTL是否足够?
- 检查授权:是否为正确账户签名?
- 检查日志:错误消息实际内容是什么?
Error Code Reference
错误代码参考
| Code | Meaning | Common Fix |
|---|---|---|
| Signature invalid | Check network passphrase |
| Wrong sequence | Reload account |
| Transaction expired | Rebuild and resubmit |
| Missing trustline | Create trustline first |
| Insufficient balance | Add funds |
| Below minimum balance | Maintain reserve |
| 代码 | 含义 | 常见修复 |
|---|---|---|
| 签名无效 | 检查网络密码 |
| 序列错误 | 重新加载账户 |
| 交易过期 | 重建并重新提交 |
| 缺失信任线 | 先创建信任线 |
| 余额不足 | 添加资金 |
| 低于最低余额 | 维持储备金 |