合约架构
概述
Zanbara 智能合约是基于 Solana 区块链和 Anchor 框架构建的永续合约交易系统核心组件。本文档详细介绍了合约的整体架构设计、技术选型和实现原理。
技术栈
Solana 区块链
Zanbara 选择 Solana 作为底层区块链平台,主要基于以下优势:
高性能:支持 50,000+ TPS,满足高频交易需求
低成本:交易费用远低于以太坊,适合频繁链上操作
快速确认:400ms 出块时间,提供接近中心化交易所的体验
并行执行:Sealevel 运行时支持并行交易处理,提升吞吐量
Anchor 框架
Anchor 是 Solana 智能合约开发的事实标准框架,提供:
类型安全:强类型账户验证和序列化
简化开发:宏驱动的开发体验,减少样板代码
自动化安全:内置账户验证、权限检查等安全机制
IDL 生成:自动生成接口定义语言文件,便于前端集成
测试工具:完整的测试框架和本地验证器支持
# Anchor.toml
[features]
seeds = false
skip-lint = false
[programs.localnet]
zanbara_dex = "BaTAwfL7msaQ2QGvN7EYqt2MKtjs6TGc1wzNN1BTP8F"
[programs.devnet]
zanbara_dex = "BaTAwfL7msaQ2QGvN7EYqt2MKtjs6TGc1wzNN1BTP8F"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"程序架构
Program ID
Zanbara 智能合约的 Program ID:
declare_id!("BaTAwfL7msaQ2QGvN7EYqt2MKtjs6TGc1wzNN1BTP8F");该地址在部署时确定,是合约在 Solana 网络上的唯一标识符。所有与合约的交互都通过此 Program ID 进行。
模块结构
Zanbara 合约采用模块化设计,将功能划分为多个独立模块:
pub mod state; // 状态定义模块
pub mod instructions; // 指令处理模块
pub mod errors; // 错误定义模块
pub mod constants; // 常量定义模块
pub mod oracle; // 预言机集成模块
pub mod risk; // 风险管理模块state 模块
定义链上存储的所有数据结构:
UserAccount:用户账户,存储用户余额、抵押品和账户状态Position:持仓记录,存储单个市场的持仓信息Market:市场配置,存储交易对参数、费率和限制
instructions 模块
实现所有可调用的合约指令:
initialize_user:初始化用户账户initialize_market:初始化交易市场(仅管理员)deposit:存入抵押品withdraw:提取资金open_position:开仓或调整持仓close_position:平仓update_price:更新市场价格(从预言机)liquidate:清算低保证金账户
oracle 模块
集成 Pyth Network 预言机:
价格数据获取和验证
时效性检查
置信度评估
价格格式转换
risk 模块
风险管理和保证金计算:
初始保证金率计算
维持保证金率计算
清算价格计算
杠杆倍数验证
程序入口点
#[program]
pub mod zanbara_dex {
use super::*;
/// 初始化用户账户
pub fn initialize_user(ctx: Context<InitializeUser>) -> Result<()> {
instructions::initialize_user::handler(ctx)
}
/// 存入抵押品
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
instructions::deposit::handler(ctx, amount)
}
/// 提取资金
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
instructions::withdraw::handler(ctx, amount)
}
/// 开仓或调整持仓
pub fn open_position(
ctx: Context<OpenPosition>,
market_index: u16,
size: i64,
is_long: bool,
) -> Result<()> {
instructions::open_position::handler(ctx, market_index, size, is_long)
}
/// 平仓
pub fn close_position(ctx: Context<ClosePosition>, market_index: u16) -> Result<()> {
instructions::close_position::handler(ctx, market_index)
}
/// 初始化市场(仅管理员)
pub fn initialize_market(
ctx: Context<InitializeMarket>,
market_index: u16,
oracle: Pubkey,
) -> Result<()> {
instructions::initialize_market::handler(ctx, market_index, oracle)
}
/// 更新市场价格
pub fn update_price(ctx: Context<UpdatePrice>, market_index: u16) -> Result<()> {
instructions::update_price::handler(ctx, market_index)
}
/// 清算低保证金持仓
pub fn liquidate(ctx: Context<Liquidate>, market_index: u16) -> Result<()> {
instructions::liquidate::handler(ctx, market_index)
}
}账户模型与 PDA
Program Derived Addresses (PDA)
Zanbara 使用 PDA 确保账户地址的确定性和安全性。PDA 是从 Program ID 和种子(seeds)派生的账户地址,无私钥,只能由程序本身控制。
优势
确定性:相同的种子始终生成相同的地址
安全性:无私钥意味着只有程序可以签名
简化查询:客户端可以计算地址,无需额外查询
种子设计
// constants.rs
pub const USER_ACCOUNT_SEED: &[u8] = b"user_account";
pub const POSITION_SEED: &[u8] = b"position";
pub const MARKET_SEED: &[u8] = b"market";
pub const VAULT_SEED: &[u8] = b"vault";PDA 派生示例
// 用户账户 PDA
// seeds = [b"user_account", user_pubkey]
let (user_account_pda, user_account_bump) = Pubkey::find_program_address(
&[USER_ACCOUNT_SEED, user.key().as_ref()],
program_id,
);
// 持仓 PDA
// seeds = [b"position", user_pubkey, market_index]
let (position_pda, position_bump) = Pubkey::find_program_address(
&[POSITION_SEED, user.key().as_ref(), &market_index.to_le_bytes()],
program_id,
);
// 市场 PDA
// seeds = [b"market", market_index]
let (market_pda, market_bump) = Pubkey::find_program_address(
&[MARKET_SEED, &market_index.to_le_bytes()],
program_id,
);账户关系图
┌─────────────────────────────────────────────────────────────┐
│ Zanbara Program │
│ (BaTAwfL7msaQ2QGvN7...BTP8F) │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Market (PDA) │ │ User Account │ │ Vault (PDA) │
│ │ │ (PDA) │ │ │
│ - ETH-PERP │ │ │ │ - Collateral │
│ - BTC-PERP │ │ - Balance │ │ - Pool │
│ - SOL-PERP │◄───┤ - Positions │───►│ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
│ │
└────────┬───────────┘
│
▼
┌──────────────┐
│Position (PDA)│
│ │
│ - Market │
│ - Size │
│ - Entry Price│
│ - Collateral │
└──────────────┘
│
▼
┌──────────────┐
│ Oracle Price │
│ (Pyth) │
│ │
│ - BTC/USD │
│ - ETH/USD │
│ - SOL/USD │
└──────────────┘状态管理
账户数据持久化
Solana 采用账户模型(Account Model),所有数据存储在账户中。Zanbara 使用 Anchor 的 #[account] 宏自动处理序列化和反序列化。
#[account]
#[derive(Default)]
pub struct UserAccount {
pub authority: Pubkey, // 32 bytes
pub collateral: u64, // 8 bytes
pub available_balance: u64, // 8 bytes
pub total_pnl: i64, // 8 bytes
pub active_positions: u16, // 2 bytes
pub cumulative_fees: u64, // 8 bytes
pub created_at: i64, // 8 bytes
pub last_updated: i64, // 8 bytes
pub bump: u8, // 1 byte
}
impl UserAccount {
// 账户空间大小计算
pub const LEN: usize = 8 + // discriminator (Anchor 自动添加)
32 + // authority
8 + // collateral
8 + // available_balance
8 + // total_pnl
2 + // active_positions
8 + // cumulative_fees
8 + // created_at
8 + // last_updated
1; // bump
}账户空间租金
Solana 要求账户支付租金(rent)以保持数据存活。Zanbara 账户在创建时支付足额租金,确保账户永久免租金。
// 创建账户时计算所需租金
let rent = Rent::get()?;
let space = UserAccount::LEN;
let lamports = rent.minimum_balance(space);并发控制
Solana 的并行执行模型允许不冲突的交易同时处理。Zanbara 通过账户设计确保:
不同用户的操作可以并行执行(不同的 UserAccount)
同一用户的不同市场持仓可以并行操作(不同的 Position)
只有涉及相同账户的交易会串行执行
跨程序调用 (CPI)
System Program CPI
用于 SOL 转账:
use anchor_lang::system_program::{transfer, Transfer};
pub fn handler(ctx: Context<Deposit>, amount: u64) -> Result<()> {
// 从用户钱包转账到用户账户 PDA
transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.user.to_account_info(),
to: ctx.accounts.user_account.to_account_info(),
},
),
amount,
)?;
Ok(())
}SPL Token Program CPI
用于 USDC 等代币操作:
use anchor_spl::token::{self, Transfer, Token, TokenAccount};
pub fn transfer_collateral(
ctx: &Context<SomeInstruction>,
amount: u64,
) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.user_token_account.to_account_info(),
to: ctx.accounts.vault_token_account.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}Pyth Oracle CPI
读取价格数据:
use pyth_sdk_solana::state::SolanaPriceAccount;
pub fn get_oracle_price(oracle_account: &AccountInfo) -> Result<u64> {
let price_feed = SolanaPriceAccount::account_info_to_feed(oracle_account)
.map_err(|_| PerpError::InvalidOracle)?;
let clock = Clock::get()?;
let current_price = price_feed
.get_price_no_older_than(clock.unix_timestamp, 60)
.ok_or(PerpError::StalePriceData)?;
Ok(current_price.price as u64)
}混合架构设计
Zanbara 采用链下撮合 + 链上结算的混合架构,平衡性能和去中心化:
链下组件
撮合引擎(基于 PeakPoilt):
高性能订单簿维护
实时订单撮合
价格发现
成交记录生成
优势:
超低延迟(< 1ms)
高吞吐量(100,000+ orders/s)
复杂订单类型支持
无链上交易费用
链上组件
智能合约:
用户资金托管
持仓状态管理
结算验证和执行
清算处理
预言机价格更新
优势:
资金安全由区块链保障
透明可审计
无需信任中心化撮合
抗审查
协作流程
用户下单
│
▼
链下撮合引擎
│
├─► 订单簿更新
├─► 价格发现
└─► 生成成交
│
▼
批量结算
│
▼
链上合约
│
├─► 验证成交合法性
├─► 更新用户余额
├─► 更新持仓状态
└─► 计算手续费安全机制
权限验证
Anchor 提供强类型的账户验证:
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
seeds = [USER_ACCOUNT_SEED, user.key().as_ref()],
bump = user_account.bump,
has_one = authority @ PerpError::Unauthorized // 验证 authority 匹配
)]
pub user_account: Account<'info, UserAccount>,
#[account(mut)]
pub authority: Signer<'info>, // 必须是签名者
pub system_program: Program<'info, System>,
}数值溢出保护
使用 Rust 的 checked 算术运算:
pub fn deposit(&mut self, amount: u64) -> Result<()> {
self.collateral = self.collateral
.checked_add(amount)
.ok_or(PerpError::ArithmeticOverflow)?;
self.available_balance = self.available_balance
.checked_add(amount)
.ok_or(PerpError::ArithmeticOverflow)?;
Ok(())
}重入攻击防护
Solana 运行时自动防止重入攻击,因为交易是原子性的,且账户锁定机制确保同一账户不会被同一交易重复修改。
预言机验证
pub fn validate_oracle_price(
oracle_price: &OraclePrice,
max_age: i64,
max_confidence_pct: u64,
) -> Result<()> {
// 检查价格时效性
require!(
!oracle_price.is_stale(max_age)?,
PerpError::StalePriceData
);
// 检查置信区间
let max_conf = (oracle_price.price.abs() as u64)
.checked_mul(max_confidence_pct)
.and_then(|v| v.checked_div(10000))
.ok_or(PerpError::ArithmeticOverflow)?;
require!(
oracle_price.conf <= max_conf,
PerpError::InvalidOracle
);
Ok(())
}升级策略
程序升级权限
Solana 程序可以设置升级权限(upgrade authority):
# 部署时指定升级权限
solana program deploy \
--program-id <PROGRAM_ID> \
--upgrade-authority <AUTHORITY_KEYPAIR> \
target/deploy/zanbara_dex.so
# 转移升级权限到多签
solana program set-upgrade-authority \
<PROGRAM_ID> \
--new-upgrade-authority <MULTISIG_PUBKEY>数据迁移
由于账户结构变更可能影响现有数据,升级时需考虑:
向后兼容:新字段添加到结构末尾
版本标识:账户包含版本号字段
迁移指令:提供数据迁移指令
#[account]
pub struct UserAccountV2 {
pub version: u8, // 新增版本字段
pub authority: Pubkey,
pub collateral: u64,
// ... 现有字段
pub new_field: u64, // 新增字段
}
pub fn migrate_user_account(ctx: Context<MigrateAccount>) -> Result<()> {
let account = &mut ctx.accounts.user_account;
if account.version < 2 {
// 执行迁移逻辑
account.new_field = 0;
account.version = 2;
}
Ok(())
}不可变部署
对于完全审计的生产版本,可以撤销升级权限,使程序变为不可变:
solana program set-upgrade-authority \
<PROGRAM_ID> \
--new-upgrade-authority none性能优化
计算单元优化
Solana 限制每笔交易的计算单元(Compute Units)。优化建议:
避免不必要的计算:缓存重复计算结果
使用高效算法:选择 O(1) 而非 O(n) 操作
减少账户加载:只加载必需的账户
批量操作:合并多个小操作为一个大操作
// 优化前:多次计算
let price1 = calculate_liquidation_price(...);
let price2 = calculate_liquidation_price(...); // 重复计算
// 优化后:缓存结果
let price = calculate_liquidation_price(...);
// 重用 price账户大小优化
减小账户大小可降低租金成本:
使用紧凑的数据类型(u16 而非 u64)
避免存储可计算的数据
使用位标志(bit flags)代替多个布尔值
// 优化前
pub struct Market {
pub is_active: bool, // 1 byte
pub is_paused: bool, // 1 byte
pub is_closed: bool, // 1 byte
// ... 3 bytes
}
// 优化后
pub struct Market {
pub status: u8, // 1 byte (0=active, 1=paused, 2=closed)
// ... 节省 2 bytes
}开发工具链
本地开发环境
# 安装 Anchor CLI
cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked
# 启动本地验证器
solana-test-validator
# 构建合约
anchor build
# 运行测试
anchor test
# 部署到本地
anchor deployIDL 生成
Anchor 自动生成 IDL(Interface Definition Language)文件:
{
"version": "0.1.0",
"name": "zanbara_dex",
"instructions": [
{
"name": "initializeUser",
"accounts": [
{ "name": "userAccount", "isMut": true, "isSigner": false },
{ "name": "user", "isMut": true, "isSigner": true },
{ "name": "systemProgram", "isMut": false, "isSigner": false }
],
"args": []
},
{
"name": "deposit",
"accounts": [
{ "name": "userAccount", "isMut": true, "isSigner": false },
{ "name": "user", "isMut": true, "isSigner": true },
{ "name": "systemProgram", "isMut": false, "isSigner": false }
],
"args": [
{ "name": "amount", "type": "u64" }
]
}
],
"accounts": [
{
"name": "UserAccount",
"type": {
"kind": "struct",
"fields": [
{ "name": "authority", "type": "publicKey" },
{ "name": "collateral", "type": "u64" },
{ "name": "availableBalance", "type": "u64" }
]
}
}
]
}前端集成
使用 Anchor TS 客户端:
import { Program, AnchorProvider, web3 } from '@coral-xyz/anchor';
import { Zanbara } from './idl/zanbara_dex';
import idl from './idl/zanbara_dex.json';
const provider = AnchorProvider.env();
const program = new Program<Zanbara>(idl as Zanbara, provider);
// 调用合约指令
const tx = await program.methods
.deposit(new BN(1000000))
.accounts({
userAccount: userAccountPda,
user: wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.rpc();相关资源
Solana 官方文档:https://docs.solana.com/
Anchor 框架文档:https://www.anchor-lang.com/
Pyth Network:https://pyth.network/developers
Solana Cookbook:https://solanacookbook.com/
Solana Program Library:https://spl.solana.com/
总结
Zanbara 智能合约架构体现了现代 DeFi 应用的最佳实践:
安全第一:多层次权限验证和数值保护
高性能:优化的计算和存储设计
可扩展:模块化架构便于功能扩展
开发者友好:强类型 + IDL + 完善的工具链
混合架构:链下性能 + 链上安全
通过深入理解这些架构设计,您将能够有效地与 Zanbara 合约集成,或基于此架构构建自己的永续合约系统。
Last updated