关于 smart-contracts
面向 Rust 开发者的 Stellar 智能合约指南,涵盖构建、测试与安全审查。除入门流程外,还按任务链接开发、测试和安全专题文档。
主要功能
- 01Rust 合约设置与流程
- 02开发模式专题参考
- 03多层级测试指南
- 04安全审查清单
- 05构建与部署指引
适用场景
- 01启动 Stellar Rust 合约项目
- 02选择合约测试层级与方法
- 03主网上线前审查合约安全
- 04排查授权或存储错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ stellar/ stellar-dev-skill --skill smart-contracts安装后在 Claude Code 中输入/smart-contracts确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
development.mdsecurity.mdtesting.md详细介绍
Stellar 智能合约
使用 Rust 构建 Stellar 智能合约的指南。Stellar 上的智能合约曾以“Soroban”作为品牌名——该平台名称已弃用,但 Rust SDK(soroban-sdk)和若干工具名称仍保留此前缀。
本文件介绍设置和核心工作流。深入内容见配套文件——请阅读与任务相符的文件:
| 任务 | 文件 |
|---|---|
| 存储/TTL、授权、构造函数、跨合约调用、代币、事件、错误、升级、工厂、治理/DeFi 模式、费用/资源、故障排查 | development.md |
| 单元、集成、模糊、属性、分叉和变异测试 | testing.md |
| 安全审查、漏洞类别、检查清单、审计准备、工具 | security.md |
何时使用此技能
- 使用 Rust 编写 Stellar 智能合约
- 设置合约测试(任何层级)
- 审查合约是否存在安全问题
- 设计可升级合约、工厂、自定义账户或 DeFi 基础组件
- 调试合约特有错误(授权、存储、归档、资源限制)
相关技能
- 资产发行、信任线和 SAC 部署 →
../assets/SKILL.md - 调用合约的前端/钱包 →
../dapp/SKILL.md - 链上数据查询(RPC/Horizon)→
../data/SKILL.md - ZK 验证(BLS12-381、Groth16、Circom/Noir/RISC Zero)→
../zk-proofs/SKILL.md - SEP/CAP 标准和生态链接 →
../standards/SKILL.md
版本
此技能基于 protocol 27(soroban-sdk v27、rs-soroban-env v27、stellar-cli v27)编写。示例中的版本号仅供参考——请从以下来源确认当前版本,不要盲信任何文档:
soroban-sdk的主版本与协议版本一致(SDK 27 ↔ protocol 27)。此规则不受任何特定版本发布的影响。- 最新 SDK 版本:crates.io/crates/soroban-sdk(或使用
cargo add soroban-sdk解析版本)。预发布版本(-rc.x)仅在协议升级期间存在,必须使用精确版本字符串固定;GitHub releases 中列出了这些版本及其变更日志。 - 网络通过验证者投票升级,顺序为 testnet 在前、mainnet 在后——请固定与部署目标网络相匹配的 SDK 主版本。当前协议版本:通过 RPC
getVersionInfo或 Stellar Lab 查询。 - 此处引用的数值网络限制为编写本文时的 mainnet 设置;它们会因投票而变更——Stellar Lab 的 Network Limits 页面 和
stellar network settings --network mainnet可显示当前值。
平台限制
合约使用 Rust 编译为 WebAssembly,并在沙箱化主机中运行:
- 必须使用
#![no_std]——使用soroban_sdk类型(String、Vec、Map、Symbol),而非 Rust 标准库 - 编译时使用
wasm32v1-none目标(Rust ≥ 1.84)——这是 Stellar 运行时支持的唯一 Wasm 目标 - 编译后的合约大小限制为 128KB(由网络配置)
Symbol限制为 32 个字符(a-zA-Z0-9_);symbol_short!()最多支持 9 个字符- 存储需要租用:每个条目都有 TTL,且可能被归档——参见 development.md
- 不支持
delegatecall,主机会阻止跨合约重入——参见 security.md - 不支持 I/O 和网络访问,除账本时间戳外也没有时钟——合约能执行的一切操作都依赖
Env
项目设置
stellar contract init my-contract # scaffolds a Cargo workspace with contracts/
cd my-contract
rustup target add wasm32v1-none # once per toolchain
Cargo.toml 基础配置(由 stellar contract init 生成):
[lib]
crate-type = ["lib", "cdylib"] # lib is needed for tests and fuzzing
[dependencies]
soroban-sdk = "27" # protocol 27, which mainnet runs at the time of writing.
# Pre-releases (`-rc.x`) exist only during a rollout and need the
# exact version string. Check crates.io for the latest release, and
# the live protocol version of your target network, before deploying.
[dev-dependencies]
soroban-sdk = { version = "27", 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
合约结构
一个精简示例,展示状态、构造函数、授权、TTL、类型化错误和事件:
#![no_std]
use soroban_sdk::{
contract, contracterror, contractevent, contractimpl, contracttype, Address, Env,
};
#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Admin,
Counter,
}
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum Error {
NotInitialized = 1,
}
// Emitted as topics ("incremented", by) with data {count} — #[topic] fields
// become topics, the rest go in the data payload.
#[contractevent]
pub struct Incremented {
#[topic]
pub by: Address,
pub count: u32,
}
#[contract]
pub struct CounterContract;
#[contractimpl]
impl CounterContract {
// Runs once, atomically, at deploy time. Must be named `__constructor`.
// Does not run again on upgrade.
pub fn __constructor(env: Env, admin: Address) {
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Counter, &0u32);
}
pub fn increment(env: Env) -> Result<u32, Error> {
let admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.ok_or(Error::NotInitialized)?;
admin.require_auth();
let count: u32 = env.storage().instance().get(&DataKey::Counter).unwrap_or(0);
let count = count + 1;
env.storage().instance().set(&DataKey::Counter, &count);
// Extend TTL so contract state is not archived (threshold, extend-to)
env.storage().instance().extend_ttl(120 * 17280, 180 * 17280);
Incremented { by: admin, count }.publish(&env);
Ok(count)
}
pub fn get_count(env: Env) -> u32 {
env.storage().instance().get(&DataKey::Counter).unwrap_or(0)
}
}
完整模式(三种存储类型、授权变体、跨合约调用、代币、自定义类型):development.md。
构建、部署、调用
# Build optimized WASM → target/wasm32v1-none/release/*.wasm
# (optimization is on by default; --optimize=false to disable)
stellar contract build
# Create and fund an identity (testnet)
stellar keys generate alice --network testnet --fund
# Deploy (constructor args go after the `--`)
stellar contract deploy \
--wasm target/wasm32v1-none/release/my_contract.wasm \
--source-account alice \
--network testnet \
-- \
--admin alice
# Invoke
stellar contract invoke \
--id CONTRACT_ID \
--source-account alice \
--network testnet \
-- \
increment
如需上传 WASM 但不实例化(例如用于工厂或升级),请使用 stellar contract upload(旧命令 stellar contract install 是已弃用的别名)。
验证你的构建
链上没有任何信息能将已部署的 WASM 与其源代码关联起来。SEP-0055(草案)弥补了这一缺口:在 GitHub Actions 中构建,将源代码仓库信息写入 WASM 的 contractmetav0 区段,并为二进制文件发布 GitHub 构建证明——这样,任何人都能从链上的 WASM 哈希追溯到生成它的提交和工作流运行记录。验证者会未经身份验证地从 api.github.com/repos/<owner>/<repo>/attestations/sha256:<wasm-hash> 读取证明,因此源代码仓库必须公开,才能让“任何人”都能查看。Free、Pro 和 Team 计划中的私有或内部仓库根本无法发布证明。Enterprise Cloud 可以发布,但只有拥有仓库访问权限的账户才能读取,因此合约的任何用户都无法读取。在任何主网部署前,都应将其作为标准步骤;它只需一个工作流文件,因此没有理由只对面向用户的合约启用。
两个元数据条目用于记录这些信息(使用 stellar contract build --meta key=value 添加,通过 stellar contract info meta --wasm <file> 读取):
source_repo=github:<owner>/<repo>— 源代码所在位置home_domain=<domain>— 提供 SEP-1stellar.toml的域名,用于组织/代币查询(可选;填写纯域名,不带协议或路径)
soroban-build-workflow 可完成整个流程——添加元数据并构建、优化、生成证明、发布附带 WASM 的发行版,并将构建注册到 stellar.expert 的合约验证系统。
请自行确定其版本,不要照抄任何文档(包括本文档)中的固定版本。固定版本会过时,而且该仓库中的最新标签不一定是已发布的版本:
REPO=stellar-expert/soroban-build-workflow
TAG=$(gh release view --repo "$REPO" --json tagName --jq .tagName) # latest release
SHA=$(gh api "repos/$REPO/commits/$TAG" --jq .sha) # its commit
gh api "repos/$REPO/contents/.github/workflows/release.yml?ref=$SHA" \
-H "Accept: application/vnd.github.raw" # the file you will pin
请读取该 SHA 对应的 release.yml,绝不要读取标签对应的文件。标签可能在这两个步骤之间移动,导致你审查的是一个提交,固定的却是另一个提交。该工作流应检出标签所指向的提交、从源代码构建,并为上传的同一文件生成证明。如果不符合这些要求,请停止并告知用户,而不是仍然固定该版本。然后固定完整 SHA,因为此工作流会使用你的仓库令牌、OIDC 和证明权限运行。
请告知用户这个固定版本的保护范围。它只固定了 release.yml,而非该文件调用的操作。这些操作使用各自可变的标签(actions/checkout@v4、CLI、证明操作),会在运行时以相同权限解析调用,而且调用方无法固定它们。如果无法接受这种剩余风险,请自行派生该工作流。
# .github/workflows/release.yml — verified build on every version tag
name: Build and Release
on:
push:
tags: ["v*"]
permissions: # inherited by the called workflow, which cannot elevate them
contents: write # create the release, attach the WASM
id-token: write # sign the attestation
attestations: write # publish it
jobs:
release:
# the SHA you resolved and reviewed; keep the version in the comment
uses: stellar-expert/soroban-build-workflow/.github/workflows/release.yml@<commit-sha> # vX.Y.Z
with:
release_name: ${{ github.ref_name }}
home_domain: example.com # optional
package: my-contract # optional — omit to build the working directory
secrets:
release_token: ${{ secrets.GITHUB_TOKEN }} # created by GitHub, nothing to configure
随后部署该发行版附带的 WASM,而不是在本地重新构建的版本——编译环境各不相同,只要哈希相差一个字节,就无法匹配任何证明。
构建结果会显示在 Stellar Lab 的合约浏览器和 stellar.expert 上。请准确告知用户它能证明什么:某次特定的工作流运行从某个提交构建了这个二进制文件。它无法说明代码是否安全,也无法证明代码曾经过审查。
最简测试
// src/test.rs — included from lib.rs with `mod test;`
#![cfg(test)]
use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env};
#[test]
fn test_increment() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let contract_id = env.register(CounterContract, (admin.clone(),));
let client = CounterContractClient::new(&env, &contract_id);
assert_eq!(client.increment(), 1);
assert_eq!(client.get_count(), 1);
}
有关授权模拟、事件断言、模糊测试、分叉测试和 CI 设置,请参阅:testing.md。
主网部署前
请按照 security.md 中的清单逐项检查——授权、重新初始化、算术、存储 TTL 和跨合约验证都是常见的故障原因。通过已验证的构建进行部署,确保链上的 WASM 哈希能够追溯到可供审查的提交。