AI Agent Gas 优化与合约性能调优实战指南
面向 AI Agent 的 msg-chain-1 智能合约 Gas 效率手册
链 ID:
msg-chain-1| 共识: DAR | 虚拟机: CosmWasm (WasmVM)
状态: 规划文档 — 主网裁决为 No-Go,所有数据均为主网预演
Gas: 1,000,000,000 attoMSG/gas
Gas 分配: 40% 验证者 / 30% 开发者 / 20% 燃烧 / 10% 基金会金库
地址格式: SHA3-512(前40位) + SHA-256 校验 | 签名: Dilithium-5 (公钥2592字节 / 私钥4864字节 / 签名4595字节)
AI Agent安全边界: 永不自主创建新合约,永不自主调整 Gas 参数,永不自主铸造/销毁代币
1. 概述
1.1 为什么 Gas 优化对 AI Agent 至关重要
AI Agent 默认倾向于生成"能跑"而非"高效"的代码。在 msg-chain-1 上,Gas 直接对应 umsg 成本,低效的存储模式、冗余的跨合约调用和不合理的查询逻辑会显著推高用户费用。
AI Agent 生成的合约可能出现的典型低效模式:
- 使用
Vec存储所有用户数据而非Map,导致每次修改读写全量数据 - 遍历整个存储集合来查询少量记录,Gas 随数据量线性增长
- 在单条消息中触发多次跨合约调用,支付多次消息调度费
- 未使用分页,前端查询时加载数千条记录
核心目标:降低用户成本、提高交易成功率(不超过 50,000,000 区块 Gas 上限)、提升区块吞吐量。
优化潜在收益:经验表明,系统性的 Gas 优化可为用户节省 60–90% 的合约交互成本。
1.2 MSG Chain Gas 模型
总 Gas = 基础交易 Gas + Wasm 执行 Gas + 存储 Gas + 跨合约调用 Gas
| 组件 | 说明 | 典型消耗 |
|---|---|---|
| 基础交易 | 签名验证、消息序列化 | ~50,000 – 100,000 |
| Wasm 执行 | 合约代码执行 | 按指令计费 |
| 存储访问 | KV 读写 | 读 ~5,000,写 ~30,000 |
| 跨合约调用 | Submessage + Reply | 每次 ~30,000 – 100,000 |
GAS_CONFIG = {
'prices': {
'price': '1000000000000000000attoMSG',
},
'block_gas_limit': 50000000,
'block_time': 5,
}
COSMWASM_GAS_COSTS = {
'store_code': '~300,000 gas per KB',
'instantiate': '~100,000 gas base',
'execute_read': '~50,000 gas',
'execute_write': '~150,000 gas',
'execute_complex': '~300,000 – 1,000,000 gas',
'query_simple': '~10,000 gas',
'query_complex': '~50,000 – 200,000 gas',
'ibc_transfer': '~200,000 gas',
'reply_handler': '~50,000 – 100,000 gas',
'submessage': '~30,000 gas base',
'storage_read_per_kb': '~5,000 gas',
'storage_write_per_kb': '~30,000 gas',
}
def estimate_tx_cost(gas_used: int, price: str = 'average') -> str:
p = {'low': 1_000_000_000, 'average': 1_000_000_000, 'high': 1_000_000_000}
cost = gas_used * p[price]
return f"{cost} attoMSG (≈ {cost / 1_000_000_000} MSG)"
1.3 优化原则速览
- 写比读贵 5–10 倍 → 优先缓存和批量写入
- 存储是最大开销 → 优化存储模式 = 直接降低 Gas
- 跨合约调用隐藏成本高 → 合并操作,减少 Submessage
- 遍历随数据量线性增长 → 始终分页
2. Gas 模型基础
2.1 Wasm 指令级 Gas
CosmWasm 的 WasmVM 对指令分级计费:
| 指令类别 | 示例 | 每指令 Gas |
|---|---|---|
| 简单算术 | i32.add, i64.sub |
1 |
| 复杂算术 | i64.mul, i64.div |
5 |
| 内存操作 | i32.load, i64.store |
10 |
| 控制流 | br, call |
2 |
| 主机函数 | cosmwasm_* 调用 |
50–500 |
2.2 存储 Gas 公式
CosmWasm 的 KV 存储基于 Merkle 树(IAVL),每次写入涉及树路径更新。
fn storage_write_gas(key_len: usize, val_len: usize, is_new: bool) -> u64 {
let base = 15000;
let key_gas = key_len as u64 * 100;
let val_gas = val_len as u64 * 20;
let penalty = if is_new { 10000 } else { 0 };
base + key_gas + val_gas + penalty
}
fn storage_read_gas(key_len: usize) -> u64 {
3000 + key_len as u64 * 50
}
关键洞察:
- 键越长越贵 — 键每增加 1 字节,读 Gas 增加 50,写 Gas 增加 100
- 新增键比更新贵 10,000 Gas — 复用已有存储槽位更经济
- 写比读贵 5–10 倍 — 优先将读密集型操作放在查询中,而非 execute
- 大 value 比大 key 影响小 — value 按 20 Gas/字节计费,key 按 100 Gas/字节
2.3 CosmWasm Gas 计费流水线
一笔交易从提交到确认经历的 Gas 计费阶段:
1. CheckTx(mempool 验证)
├── 反序列化交易: ~5,000 Gas
├── 验证签名: ~10,000 Gas
├── 基本格式检查: ~5,000 Gas
└── 非执行验证: 通过则计入区块
2. DeliverTx(实际执行)
├── WasmVM 初始化: ~15,000 Gas
├── 消息路由: ~5,000 Gas
├── 合约执行: ~50,000 – ∞ Gas(取决于逻辑)
│ ├── 存储读取: 每次 ~5,000 Gas
│ ├── 存储写入: 每次 ~30,000 Gas
│ ├── Wasm 指令: 按条计费
│ └── 主机函数: 与链交互每次 50–500 Gas
├── 子消息(可选): ~30,000 – 100,000 Gas/次
├── Reply(可选): ~50,000 – 100,000 Gas
└── 序列化 Response: ~5,000 Gas
3. 后处理
├── 事件日志写入: ~2,000 Gas/事件
└── 手续费扣除: ~3,000 Gas
理解此流水线有助于 AI Agent 识别 Gas 热点:存储操作和跨合约调用占全部 Gas 的 70–90%。
2.3 Gas 估算模型
def estimate_execute_gas(
reads: int, writes: int, submsgs: int,
data_kb: int, complex: bool = False
) -> int:
gas = 75000 + 30000 # base + wasm overhead
gas += reads * 5000
gas += writes * 30000
gas += submsgs * 25000
gas += data_kb * 10000
if complex:
gas = int(gas * 1.5)
return gas
2.4 区块容量
每区块 50,000,000 Gas,不同操作类型的区块容量:
| 操作类型 | 单笔 Gas | 每区块笔数 | TPS |
|---|---|---|---|
| 简单转账 | 100,000 | 500 | 100 |
| 存储写入 | 350,000 | 142 | 28 |
| 复杂操作 | 800,000 | 62 | 12 |
| 批量操作 | 2,000,000 | 25 | 5 |
3. 存储优化
3.1 Map vs Vector
// ❌ BAD: Vec 在 Item 中 — 修改任意元素需读写整个数组
pub struct InefficientContract {
pub all_positions: Item<Vec<UserPosition>>,
pub user_profiles: Map<&'static [u8], UserProfile>, // value 过大
}
// ✅ GOOD: Map 的 O(1) 键值访问 + 冷热分离
pub struct EfficientContract {
pub user_count: Item<u64>,
pub user_balance: Map<&'static Addr, Uint128>, // 高频 → 轻量
pub user_stakes: Map<&'static Addr, Uint128>,
pub user_positions: Map<(&'static Addr, u64), Position>, // 分页
pub user_position_count: Map<&'static Addr, u64>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Position {
pub token: String,
pub amount: Uint128,
pub entry_price: Uint128,
pub timestamp: u64,
}
核心原则:Vec 在 Item 中 → 每次修改读写全量;Map<K, V> → O(1) 访问,天然分页。
3.2 存储模式决策树
AI Agent 在选择存储模式时可参考以下决策树:
需要持久化存储?
├── 否 → 使用内存变量(零存储 Gas)
└── 是 → 是否为单个全局值?
├── 是 → Item<T>
└── 否 → 数据量是否超过 100 条?
├── 否 → Vec 在 Item 中可接受
└── 是 → Map<K, V>
├── 需要二级索引?
│ ├── 是 → IndexedMap(方便但写入略贵)
│ └── 否 → Map + prefix 迭代
└── 需要复合键?
├── 是 → Map<(K1, K2), V>
└── 否 → Map<K, V>
3.3 键设计模式
// ✅ 短键名 + 前缀枚举
pub mod prefixes {
pub const BALANCE: &[u8] = b"b";
pub const STAKE: &[u8] = b"s";
pub const REWARD: &[u8] = b"r";
pub const ORDER: &[u8] = b"o";
}
// ✅ 复合键实现分区
const BALANCE: Map<(&Addr, &Addr), Uint128> = Map::new("b");
// ❌ 长键名浪费 Gas
// const BALANCE: Map<String, Uint128> = Map::new("user_balance_for_each_token_pair");
// ✅ 辅助 Map 替代 IndexedMap(按需索引,节省写入 Gas)
const USERS_BY_TOKEN: Map<(&Addr, &Addr), bool> = Map::new("ubt");
pub fn set_balance(store: &mut dyn Storage, user: &Addr, token: &Addr, amount: Uint128) -> StdResult<()> {
BALANCE.save(store, (user, token), &amount)?;
if amount > Uint128::zero() {
USERS_BY_TOKEN.save(store, (token, user), &true)?;
} else {
USERS_BY_TOKEN.remove(store, (token, user));
}
Ok(())
}
3.4 Snapshot 模式:减少重复读取
当合约需要多次读取同一数据时,使用 Snapshot 模式缓存结果:
// ✅ GOOD: Snapshot 模式 — 一次读取,多次使用
pub fn execute_complex_operation(
deps: DepsMut,
_env: Env,
info: MessageInfo,
) -> StdResult<Response> {
let user = &info.sender;
// 只读取一次 UserCore
let snapshot = USER_CORE.load(deps.storage, user)?;
// 后续所有操作使用内存中的 snapshot,不触及存储
let available = snapshot.balance - snapshot.staked;
// ... 使用 available 进行计算 ...
// ... 使用 snapshot 的多个字段 ...
// 最后写回(如果修改了)
USER_CORE.save(deps.storage, user, &snapshot)?;
Ok(Response::new())
}
// ❌ BAD: 每次需要数据都重新读取存储
pub fn bad_complex_operation(
deps: DepsMut,
_env: Env,
info: MessageInfo,
) -> StdResult<Response> {
let user = &info.sender;
// 第一次读取
let balance = USER_CORE.load(deps.storage, user)?.balance;
// 第二次读取(相同的存储访问!)
let staked = USER_CORE.load(deps.storage, user)?.staked;
// ... 更多操作再次读取 ...
Ok(Response::new())
}
关键点:SnapShot 模式将 N 次存储读取降为 1 次,对于有 3–5 个字段的结构体可节省 60–80% 的读取 Gas。
3.5 数据结构精简
// ❌ BAD: 大而全的结构体(序列化 ~350+ 字节 → ~12,000 Gas/次)
pub struct FullUserData {
pub address: String, pub email: String, pub display_name: String,
pub avatar_url: String, pub bio: String,
pub balance: Uint128, pub staked: Uint128, pub rewards: Uint128,
pub last_active: u64, pub created_at: u64, pub is_verified: bool,
pub referral_count: u32, pub flags: Vec<bool>,
}
// ✅ GOOD: 冷热数据分离
// 高频(每次 execute 都读)
pub struct UserCore {
pub balance: Uint128, // 16 字节
pub staked: Uint128, // 16 字节
pub rewards: Uint128, // 16 字节
pub flags: u32, // 4 字节(位标志)
}
// ~60 字节 → ~3,000 Gas
// 低频(仅特定查询)
pub struct UserProfile {
pub email: String, pub display_name: String, pub avatar_url: String, pub bio: String,
}
// ✅ 使用位标志代替 Vec<bool>
pub struct UserFlags(u32);
impl UserFlags {
pub const IS_VERIFIED: u32 = 1 << 0;
pub const IS_FROZEN: u32 = 1 << 1;
pub fn set(&mut self, flag: u32) { self.0 |= flag; }
pub fn has(&self, flag: u32) -> bool { self.0 & flag != 0 }
}
// ✅ 最小数值类型
let _: u64 = 42; // 8 字节 ✓
let _: Uint128 = Uint128::zero(); // 16 字节 ✓(支持 ~3.4e38)
// ❌ Uint256 不必要地消耗双倍空间
3.6 批量操作模式
// ✅ GOOD: 批量消息 — 单次消息处理 N 个操作
#[cw_serde]
pub enum ExecuteMsg {
BatchTransfer { recipients: Vec<(Addr, Uint128)> },
BatchUpdatePositions { updates: Vec<PositionUpdate> },
}
// ✅ GOOD: 仅值变化时写入
pub fn update_balance(store: &mut dyn Storage, user: &Addr, new: Uint128) -> StdResult<()> {
let old = BALANCES.load(store, user).unwrap_or_default();
if new != old {
BALANCES.save(store, user, &new)?; // 值未变 → 跳过写入
}
Ok(())
}
3.7 分页策略
use cw_storage_plus::Bound;
const MAX_PAGE_SIZE: u32 = 100;
const DEFAULT_PAGE_SIZE: u32 = 30;
// ✅ GOOD: 分页查询 — Gas 不随数据量增长
pub fn query_positions(
store: &dyn Storage, user: &Addr,
start_after: Option<u64>, limit: Option<u32>,
) -> StdResult<Vec<(u64, Position)>> {
let limit = limit.unwrap_or(DEFAULT_PAGE_SIZE).min(MAX_PAGE_SIZE) as usize;
let start = start_after.map(Bound::exclusive);
POSITIONS.prefix(user)
.range(store, start, None, Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()
}
// ❌ BAD: 加载所有数据 — Gas 随数据量线性增长
pub fn bad_query_all(store: &dyn Storage, user: &Addr) -> StdResult<Vec<(u64, Position)>> {
POSITIONS.prefix(user)
.range(store, None, None, Order::Ascending)
.collect::<StdResult<Vec<_>>>()
}
3.8 存储清理
// ✅ GOOD: 提供清理接口,删除无用存储条目
pub fn execute_clear_user(deps: DepsMut, info: MessageInfo) -> StdResult<Response> {
let user = &info.sender;
BALANCES.remove(deps.storage, user);
let keys: Vec<u64> = POSITIONS.prefix(user)
.keys(deps.storage, None, None, Order::Ascending)
.collect::<StdResult<Vec<_>>>()?;
for k in keys { POSITIONS.remove(deps.storage, (user, k))?; }
POSITION_COUNT.remove(deps.storage, user);
Ok(Response::new().add_attribute("action", "clear_user"))
}
// ✅ 缓存 TTL 模式
pub struct CachedData<T> {
pub data: T,
pub expires_at: u64,
}
pub const PRICE_CACHE: Map<&str, CachedData<Uint128>> = Map::new("pc");
pub fn get_cached_price(store: &dyn Storage, symbol: &str, height: u64) -> StdResult<Option<Uint128>> {
if let Some(c) = PRICE_CACHE.may_load(store, symbol)? {
if c.expires_at > height { return Ok(Some(c.data)); }
}
Ok(None)
}
3.9 存储优化优先级
1. ⭐ Map 替代 Vec → 节省 60–80%
2. ⭐ 冷热数据分离 → 节省 40–60%
3. ⭐ 分页查询 → 避免 OOG
4. 短键名 + 前缀枚举 → 节省 10–20%
5. 条件写入(值不变不写) → 节省 30–50%
6. 位标志替代 Vec<bool> → 节省 20–30%
7. 最小数值类型 → 节省 10–15%
8. 清理无用数据 → 长期维护成本降低
9. 缓存高频查询 → 节省 50–70%
10. 批量操作 → 节省 40–60%
4. 消息传递优化
跨合约调用是 Gas 消耗的隐藏杀手。每次 Submessage 都涉及完整的消息生命周期:序列化、路由、目标合约执行、结果返回。AI Agent 必须理解这些调用的真实成本。
4.1 消息类型成本对比
消息类型 | Gas 成本 | 说明
-----------------------------|--------------|-------------------------------------
CosmosMsg::Bank (转账) | ~50,000 | 原生代币转账,最高效
WasmMsg::Execute (同合约) | ~60,000 | 自调用,无跨合约开销
WasmMsg::Execute (跨合约) | ~100,000+ | 目标合约执行 Gas
SubMsg (无 Reply) | ~130,000+ | 基础 25,000 + 目标执行
SubMsg (ReplyOn::Success) | ~200,000+ | 含 Reply 处理
SubMsg (ReplyOn::Always) | ~250,000+ | 最高成本,谨慎使用
WasmMsg::Instantiate | ~200,000+ | 创建新合约
IbcMsg::Transfer | ~300,000+ | IBC 跨链转账
4.2 最小化跨合约调用
// ❌ BAD: 每条消息都触发跨合约调用
pub fn bad_mint(deps: DepsMut, info: MessageInfo, token_id: String, minter: String) -> StdResult<Response> {
Ok(Response::new().add_message(WasmMsg::Execute {
contract_addr: minter,
msg: to_binary(&MintMsg::Mint { token_id })?,
funds: vec![],
}))
}
// ✅ GOOD: 批量跨合约调用
pub fn good_batch_mint(deps: DepsMut, info: MessageInfo, minter: String, ids: Vec<String>) -> StdResult<Response> {
Ok(Response::new().add_message(WasmMsg::Execute {
contract_addr: minter,
msg: to_binary(&MintMsg::BatchMint { tokens: ids })?,
funds: vec![],
}))
}
// ✅ GOOD: 复合操作合并到同一合约
pub fn execute_deposit_stake_claim(deps: DepsMut, info: MessageInfo, amount: Uint128) -> StdResult<Response> {
let user = &info.sender;
// 在单次 execute 中完成三步 → 节省 2 次基础交易 Gas
let mut core = USER_CORE.load(deps.storage, user)?;
core.balance += amount;
core.staked += amount;
// claim rewards
let rewards = core.rewards;
core.rewards = Uint128::zero();
core.balance += rewards;
USER_CORE.save(deps.storage, user, &core)?;
Ok(Response::new().add_attribute("action", "deposit_stake_claim"))
}
4.3 消息设计模式:合并 vs 拆分
AI Agent 在设计消息接口时,需要权衡灵活性(小消息)和效率(大消息):
// ✅ GOOD: 分层消息设计
// 提供细粒度消息(灵活)和粗粒度复合消息(高效)
#[cw_serde]
pub enum ExecuteMsg {
// 细粒度 — 单独操作
Deposit { amount: Uint128 },
Stake { amount: Uint128 },
Claim {},
// ✅ 粗粒度 — 复合操作(推荐使用)
DepositAndStake { amount: Uint128 },
StakeAndClaim {},
DepositStakeClaim { deposit: Uint128 },
}
// 在客户端中:默认推荐使用复合操作
// 前端 UI 可以根据用户行为模式推荐最合适的复合消息
决策原则:
- 高频操作路径 → 提供复合消息(节省 30–50% Gas)
- 低频或组合不确定 → 提供细粒度消息
- AI Agent 应默认为每个功能生成"单一消息版本"和"复合版本"
4.4 Submessage Gas 核算
Submessage 成本 = 基础 ~25,000 + 序列化 ~5,000 + 目标执行 Gas + Reply ~75,000
// ✅ GOOD: 不需要 Reply 时使用 add_message 而非 add_submessage
// 节省 ~75,000 Gas/次
// ✅ ReplyOn 策略
let submsg = SubMsg {
msg: msg.into(),
gas_limit: Some(100_000),
reply_on: ReplyOn::Success, // 仅成功时触发,比 Always 省 Gas
id: 1,
payload: Binary::default(),
};
// ❌ ReplyOn::Always 是最昂贵的选项
4.5 Gas 限制策略
// ✅ GOOD: 为 Submessage 设置精确的 Gas 限制
// 好处:防止子消息 Out-of-Gas、提升父消息确定性
/// 根据操作类型返回推荐的 Gas 限制
pub fn suggested_gas_limit(op: &str) -> Option<u64> {
match op {
"transfer" => Some(60_000),
"swap" => Some(200_000),
"stake" => Some(150_000),
"batch_swap" => Some(500_000),
"oracle_query" => Some(80_000),
"nft_mint" => Some(300_000),
_ => None, // 未知操作不设限制
}
}
// ❌ BAD: 不设 Gas 限制
// 子消息可能消耗父消息的 Gas,导致整个交易 OOG
SubMsg {
msg: msg.into(),
gas_limit: None, // ❌ 依赖默认限制,风险高
reply_on: ReplyOn::Success,
id: 1,
payload: Binary::default(),
}
4.6 Reply 处理器优化
// ✅ GOOD: 轻量 Reply 处理器
pub fn reply_handler(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
1 => {
match msg.result {
SubMsgResult::Ok(_) => Ok(Response::new().add_attribute("result", "ok")),
SubMsgResult::Err(e) => Ok(Response::new().add_attribute("error", e)),
}
}
_ => Err(StdError::generic_err("Unknown reply id")),
}
}
// ❌ BAD: Reply 中执行复杂逻辑 — 容易 OOG
// ❌ BAD: Reply 中再次读写大量存储
4.7 子消息 Gas 限制
// ✅ GOOD: 设置明确的 Gas 限制,防止子消息消耗过多 Gas
pub enum GasBudget {
SimpleRead = 50_000,
SimpleWrite = 100_000,
ComplexOp = 300_000,
BatchOp = 500_000,
}
pub fn budgeted_submsg(msg: impl Into<CosmosMsg<Empty>>, budget: GasBudget) -> SubMsg {
SubMsg {
msg: msg.into(),
gas_limit: Some(budget as u64),
reply_on: ReplyOn::Error, // 仅失败时回复
id: 0,
payload: Binary::default(),
}
}
5. 查询优化
查询不直接消耗用户 Gas,但消耗节点资源并影响用户体验。高效的查询设计是合约质量的重要指标。
5.1 查询 Gas 对比
查询类型 | Gas 消耗 | 数据量 100 条时
----------------------------|------------------|-----------------
O(1) 直接键查询 | ~5,000 | ~5,000
O(log N) 范围查询(分页) | ~35,000 + N*2,000 | ~235,000
O(N) 全表扫描 | ~5,000 * N | ~500,000
O(N*M) 嵌套查询 | ~5,000 * N * M | OOG ❌
5.2 批量查询
// ❌ BAD: N 次独立存储访问 = N 倍读取 Gas
pub fn bad_snapshot(deps: Deps, user: Addr) -> StdResult<UserSnapshot> {
let balance = BALANCES.load(deps.storage, &user)?;
let stake = STAKES.load(deps.storage, &user)?;
let rewards = REWARDS.load(deps.storage, &user)?;
Ok(UserSnapshot { balance, stake, rewards })
}
// ✅ GOOD: 聚合加载函数 — 单次调用获取所有相关数据
impl UserSnapshot {
pub fn load(store: &dyn Storage, user: &Addr) -> StdResult<Self> {
Ok(Self {
balance: BALANCES.load(store, user).unwrap_or_default(),
stake: STAKES.load(store, user).unwrap_or_default(),
rewards: REWARDS.load(store, user).unwrap_or_default(),
})
}
}
5.3 SmartQuery vs RawQuery
// ✅ GOOD: 合约间通信优先使用 RawQuery(跳过反序列化)
pub fn get_raw_balance(
querier: &QuerierWrapper, contract: String, user: &Addr,
) -> StdResult<Vec<u8>> {
let key = [b"b", b"\x00", user.as_bytes()].concat();
querier.query(&QueryRequest::Wasm(WasmQuery::Raw {
contract_addr: contract,
key: key.into(),
}))
}
| 特性 | SmartQuery | RawQuery |
|---|---|---|
| Gas | 较高(反序列化) | 较低(原始字节) |
| 类型安全 | 是 | 需手动解析 |
| 适用场景 | API/前端 | 合约内部查询 |
5.4 查询缓存
// ✅ GOOD: 缓存高频查询结果(如价格预言机)
pub struct CachedPrice {
pub price: Uint128,
pub updated_at: u64,
}
pub fn get_price(store: &dyn Storage, querier: &QuerierWrapper, symbol: &str, height: u64, ttl: u64) -> StdResult<Uint128> {
if let Some(cached) = PRICE_CACHE.may_load(store, symbol)? {
if height < cached.updated_at + ttl {
return Ok(cached.price); // 缓存命中 → 零外部查询
}
}
let price = query_oracle(querier, symbol)?;
PRICE_CACHE.save(store, symbol, &CachedPrice { price, updated_at: height })?;
Ok(price)
}
5.5 查询复杂度管理
// ❌ BAD: O(N) 全表扫描 — 10,000 用户时消耗 50,000,000+ Gas
pub fn bad_all_balances(deps: Deps) -> StdResult<Vec<(Addr, Uint128)>> {
BALANCES.range(deps.storage, None, None, Order::Ascending).collect::<StdResult<Vec<_>>>()
}
// ✅ GOOD: O(1) 直接查询 + 分页列表
pub fn good_balance(deps: Deps, user: Addr) -> StdResult<Uint128> {
BALANCES.load(deps.storage, &user) // O(1) — 一次存储读取
}
// ✅ GOOD: 聚合计数器避免实时统计
pub struct AggregatedStats {
pub high_balance_count: Item<u64>,
pub total_balance: Item<Uint128>,
}
impl AggregatedStats {
pub fn on_balance_change(&self, store: &mut dyn Storage, old: Uint128, new: Uint128) -> StdResult<()> {
let mut total = self.total_balance.load(store)?;
total = total - old + new;
self.total_balance.save(store, &total)?;
Ok(())
}
}
5.6 查询接口设计原则
好的查询接口设计不仅提升用户体验,还降低节点负载:
// ✅ GOOD: 灵活的查询接口
#[cw_serde]
pub enum QueryMsg {
// 单用户 O(1) 查询
Balance { user: String },
// 批量用户查询(明确指定用户集,有限范围)
Balances { users: Vec<String> },
// 分页查询所有用户
AllBalances {
start_after: Option<String>,
limit: Option<u32>,
},
// 聚合统计 — 由计数器提供,无需遍历
Stats {},
}
// ✅ GOOD: 查询响应精简,只含必要字段
#[derive(Serialize, Deserialize)]
pub struct UserSummaryResponse {
pub balance: Uint128,
pub staked: Uint128,
// ❌ 不包含 full_history: Vec<Transaction> — 可通过专用查询获取
}
5.7 查询性能监控
AI Agent 应在合约中内置查询性能监控点:
/// 查询性能日志(用于 AI Agent 分析)
pub struct QueryMetrics {
pub query_type: &'static str,
pub items_returned: u32,
pub storage_accesses: u32,
}
impl QueryMetrics {
pub fn record(&self) {
// 在测试环境中输出查询性能指标
// 生产环境中建议移除或通过 feature flag 控制
debug!(
"Query: type={}, items={}, storage_accesses={}",
self.query_type, self.items_returned, self.storage_accesses,
);
}
}
1. 最小数据 — 只返回需要的字段
2. 分页默认 — 所有列表查询支持 start_after + limit
3. O(1) 优先 — 直接键访问优先于遍历
4. 聚合缓存 — 高频统计值预计算
5. 深度控制 — 限制嵌套查询
6. 合约实例化与升级优化
6.1 实例化优化
// ❌ BAD: 实例化时预创建大量数据
pub fn bad_instantiate(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg) -> StdResult<Response> {
for token in &msg.initial_tokens {
TOKEN_INFO.save(deps.storage, token, &TokenInfo::default())?; // 可能 1000+ 条
}
Ok(Response::new())
}
// ✅ GOOD: 惰性初始化 — 需要时创建
pub fn good_instantiate(deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg) -> StdResult<Response> {
CONFIG.save(deps.storage, &Config { owner: info.sender, paused: false })?;
TOKEN_COUNT.save(deps.storage, &0)?; // 计数器,而非预创建
Ok(Response::new().add_attribute("action", "instantiate"))
}
// ✅ GOOD: 工厂模式复用代码
pub fn execute_create_child(deps: DepsMut, info: MessageInfo, code_id: u64, label: String, msg: Binary) -> StdResult<Response> {
Ok(Response::new().add_message(WasmMsg::Instantiate {
admin: Some(info.sender.to_string()),
code_id, msg, label,
funds: vec![],
}).add_attribute("action", "create_child"))
}
6.2 代码大小优化
合约代码大小直接影响 store_code 的 Gas 成本(~300,000 Gas/KB):
# Cargo.toml: 优化编译选项
[profile.release]
opt-level = "z" # 优化大小
lto = true # 链接时优化
codegen-units = 1 # 单代码生成单元
strip = true # 移除符号信息
panic = "abort" # 移除 panic 格式化
// ✅ GOOD: 避免不必要的依赖增加合约体积
// ❌ BAD: 引入整个库仅使用一个函数
// use cosmwasm_std::Uint256; // 不必要时避免
// use cw20::Cw20Coin; // 非 cw20 合约时避免
// ✅ GOOD: 使用 #[cfg(not(target_arch = "wasm32"))] 隔离测试代码
#[cfg(not(target_arch = "wasm32"))]
mod tests {
// 测试代码不会编译进 Wasm 二进制
}
6.3 迁移优化
// ❌ BAD: 全量迁移 — 大数据量时 OOG
pub fn bad_migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
let old_data: Vec<_> = OLD_STORAGE.range(deps.storage, None, None, Order::Ascending).collect::<StdResult<Vec<_>>>()?;
for (key, old) in old_data {
NEW_STORAGE.save(deps.storage, key, &NewFormat::from_old(old))?;
}
Ok(Response::new())
}
// ✅ GOOD: 惰性迁移 — 仅更新版本号,数据按需转换
pub fn good_migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
VERSION.save(deps.storage, &CONTRACT_VERSION)?;
Ok(Response::new().add_attribute("action", "migrate").add_attribute("version", CONTRACT_VERSION))
}
// ✅ GOOD: 按需迁移模式
pub fn migrate_on_access(store: &mut dyn Storage, user: &Addr) -> StdResult<UserDataNew> {
if let Some(data) = USER_DATA_NEW.may_load(store, user)? { return Ok(data); }
let old = USER_DATA_OLD.load(store, user)?;
let new = UserDataNew::from_old(&old);
USER_DATA_NEW.save(store, user, &new)?;
USER_DATA_OLD.remove(store, user);
Ok(new)
}
6.4 升级友好的合约设计
AI Agent 应从一开始就考虑合约的可升级性,避免未来迁移的高额 Gas:
// ✅ GOOD: 使用版本号跟踪
pub const CONTRACT_VERSION: &str = "1.2.0";
pub const VERSION: Item<String> = Item::new("v");
// ✅ GOOD: 配置可升级(非硬编码)
pub struct Config {
pub owner: Addr,
pub paused: bool,
pub fee_rate: u64, // 可变
pub min_stake: Uint128, // 可变
pub emergency_addr: Option<Addr>, // 可变
}
// ✅ GOOD: 预留迁移数据槽
pub struct MigrateState {
pub previous_version: String,
pub current_version: String,
pub migrated_at: u64,
pub migrated_count: u64,
}
pub const MIGRATE_STATE: Item<MigrateState> = Item::new("ms");
// ❌ BAD: 硬编码常量 — 修改需要完整迁移
const FEE_RATE: u64 = 25; // 修改此值需重新部署
const MIN_STAKE: Uint128 = Uint128::new(1000); // 同前
6.5 迁移策略对比
| 策略 | Gas 消耗 | 适用场景 |
|---|---|---|
| 全量迁移 | O(N) — 极易 OOG | 数据量 < 100 条 |
| 惰性迁移 | O(1) | 推荐默认 |
| 按需迁移 | O(1) per access | 最推荐,用户无感 |
7. AI Agent Gas 最佳实践
7.1 AI Agent 代码生成策略
AI Agent 在生成 CosmWasm 合约代码时,应遵循以下策略确保 Gas 效率:
策略 1: "默认高效"原则
默认使用 Map 而非 Vec,默认使用分页,默认使用惰性初始化。
仅在确需灵活性的场景使用替代模式。
策略 2: "冷热分离"原则
分析数据结构访问频率,将高频字段(余额、质押)和低频字段(简介、设置)分离。
同一 execute 中多次读取的结构体使用 Snapshot 模式。
策略 3: "批量优先"原则
为所有涉及循环的操作提供批量接口。
复合操作(如 存款+质押+领取)提供单一消息入口。
策略 4: "懒惰求值"原则
实例化时不创建默认数据,迁移时不转换所有数据。
按需创建、按需转换、按需加载。
策略 5: "估算先行"原则
在生成代码前使用 Gas 估算器评估关键路径的 Gas 消耗。
确保所有路径不会接近区块 Gas 上限 50,000,000。
策略 6: "测试验证"原则
为每个合约编写 Gas 基准测试。
持续对比优化前后的 Gas 消耗变化。
7.2 Gas 优化检查清单
存储优化:
☐ 使用 Map 替代 Vec 存储集合
☐ 所有列表查询实现分页(default=30, max=100)
☐ 短键名前缀(单字节最佳)
☐ 冷热数据分离(大结构体拆分)
☐ 位标志替代多个 bool 字段
☐ 最小数值类型(u64 > Uint128 > Uint256)
☐ 值未变化时不写入
☐ 及时清理无用存储
消息传递:
☐ 批量操作封装为单条消息
☐ 无 Reply 需求时用 add_message 而非 add_submessage
☐ Submessage 设合理的 Gas 限制
☐ ReplyOn 避免使用 Always
☐ 合并多次跨合约调用为批量
查询优化:
☐ 优先 RawQuery 进行内部查询
☐ 高频查询结果缓存
☐ 响应只含必要字段
☐ 避免 O(N) 遍历 → 用分页或聚合计数器
合约设计:
☐ 实例化使用惰性初始化
☐ 迁移使用惰性/按需模式
☐ 工厂模式复用合约代码
☐ 关键参数可配置(存储在 Item 中)
7.3 Gas 消耗基准
| 操作 | Gas 消耗 |
|---|---|
Item::load (8 字节) |
~3,000 |
Item::save (8 字节) |
~18,000 |
Map::load (短键, 16 字节值) |
~5,000 |
Map::save (短键, 16 字节值) |
~30,000 |
Map::range (前 10 条) |
~35,000 |
Map::range (100 条) |
~200,000 |
| Submessage (无 Reply) | ~25,000 |
| Submessage (有 Reply) | ~100,000 |
| IBC Transfer | ~200,000 |
| JSON 序列化 (1 KB) | ~10,000 |
| JSON 反序列化 (1 KB) | ~15,000 |
参考: msg-chain-1 flat 1,000,000,000 attoMSG/gas → 典型转账 ~150,000 Gas = 150,000,000,000,000,000 attoMSG (≈0.15 MSG)
7.4 Gas 测试与验证
AI Agent 在生成合约后,应通过以下步骤验证 Gas 效率:
验证流程:
1. 静态分析: 用 ContractPatternAnalyzer 检查代码模式
2. 预估: 使用 GasEstimator 估算关键路径 Gas
3. 基准测试: 在测试网执行并记录实际 Gas
4. 对比: 实际 Gas 与估算值偏差 < 20%
5. 优化: 对超出预算的路径进行重构
6. 回归: 确认优化未引入新问题
#[cfg(test)]
mod gas_benchmarks {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
/// Gas 基准测试 — 确保关键路径在预算内
#[test]
fn benchmark_batch_transfer() {
let mut deps = mock_dependencies();
let env = mock_env();
// 准备:设置 10 个用户的余额
let user = Addr::unchecked("sender");
USER_CORE.save(&mut deps.storage, &user, &UserCore {
balance: Uint128::new(1_000_000),
staked: Uint128::zero(),
flags: 0,
}).unwrap();
// 执行:批量转账给 20 个接收方
let recipients: Vec<(String, Uint128)> = (0..20)
.map(|i| (format!("user{}", i), Uint128::new(1000)))
.collect();
let info = mock_info("sender", &[]);
let result = execute_batch_transfer(
deps.as_mut(), info, recipients
);
assert!(result.is_ok());
// 验证:批量转账消耗应 < 单次转账 * 20
// 实际的 Gas 测量需要在集成测试环境中进行
// 这里作为结构性验证
}
}
7.5 msg-chain-1 Gas 速查表
"""
msg-chain-1 Gas 速查表
Gas 价格:
flat: 1,000,000,000 attoMSG/gas → 150k Gas = 150,000,000,000,000,000 attoMSG (≈0.15 MSG)
区块容量:
最大 Gas: 50,000,000
出块时间: 5 秒
理论 TPS: ~100(简单转账场景)
常见操作成本:
操作 Gas low(umsg) avg(umsg) high(umsg)
─────────────────────────────────────────────────────────────────
简单转账 150,000 1,500 3,750 6,000
代币交换 500,000 5,000 12,500 20,000
质押/解押 250,000 2,500 6,250 10,000
领取奖励 200,000 2,000 5,000 8,000
批量转账 (20 笔) 600,000 6,000 15,000 24,000
合约部署 (100KB) 30,000,000 300,000 750,000 1,200,000
阈值警报:
⚡ Gas < 200,000: 绿色 — 高效
⚡ Gas 200k–500k: 黄色 — 正常
⚡ Gas 500k–1M: 橙色 — 需要审查
⚡ Gas 1M–5M: 红色 — 建议优化
⚡ Gas > 5M: 危险 — 极可能 OOG
"""
7.6 AI Agent Gas 估算器
class GasEstimator:
STORAGE_READ = 5_000
STORAGE_WRITE = 30_000
ITER_STEP = 2_000
SUBMSG = 25_000
REPLY = 75_000
BASE = 75_000
WASM = 30_000
def estimate(self, reads=0, writes=0, iter_steps=0, submsgs=0, replies=0, complex_logic=False, data_kb=0):
gas = self.BASE + self.WASM
gas += reads * self.STORAGE_READ
gas += writes * self.STORAGE_WRITE
gas += iter_steps * self.ITER_STEP
gas += submsgs * self.SUBMSG
gas += replies * self.REPLY
gas += data_kb * 10_000
if complex_logic:
gas = int(gas * 1.5)
return gas
def cost_attoMSG(self, gas, price='average'):
p = {'low': 1_000_000_000, 'average': 1_000_000_000, 'high': 1_000_000_000}
return gas * p[price]
7.7 AI Agent 自动分析工具
class ContractPatternAnalyzer:
PATTERNS = {
"full_table_scan": {
"risk": "HIGH",
"suggestion": "Add pagination with start_after + limit",
"saving": "up to 90%",
},
"vec_in_item": {
"risk": "HIGH",
"suggestion": "Replace Vec with Map<K, V>",
"saving": "up to 80%",
},
"large_struct": {
"risk": "MEDIUM",
"suggestion": "Split into hot/cold structs",
"saving": "up to 60%",
},
"missing_pagination": {
"risk": "MEDIUM",
"suggestion": "Add start_after and limit to list queries",
"saving": "prevents OOG",
},
}
@classmethod
def analyze(cls, code: str):
findings = []
if ".range(" in code and "start_after" not in code:
findings.append({**cls.PATTERNS["full_table_scan"]})
if "Item<Vec<" in code:
findings.append({**cls.PATTERNS["vec_in_item"]})
if "Uint256" in code or code.count("String,") > 5:
findings.append({**cls.PATTERNS["large_struct"]})
if "fn query" in code.lower() and "start_after" not in code:
findings.append({**cls.PATTERNS["missing_pagination"]})
return findings
7.8 msg-chain-1 专属提示
"""
推荐 Gas 预算(msg-chain-1):
简单转账/更新: 100,000 – 200,000 Gas
质押/领取: 200,000 – 400,000 Gas
交换/AMM: 400,000 – 800,000 Gas
批量操作 (>10): 500,000 – 2,000,000 Gas
合约部署: ~3,000,000 + 代码大小
Gas 节省速算:
减少 1 次存储写入 ≈ 节省 30,000 Gas ≈ 0.75 umsg
减少 1 次存储读取 ≈ 节省 5,000 Gas ≈ 0.125 umsg
合并 2 次消息为 1 次 ≈ 节省 75,000 Gas ≈ 1.875 umsg
缓存外部查询 1 次 ≈ 节省 50,000 Gas ≈ 1.25 umsg
避免全表扫描 1 次 ≈ 节省 ∞ Gas(可能 OOG)
AI Agent 自我检查:
✓ Map 是否使用短前缀?
✓ 所有 range 是否有 limit?
✓ 大结构体是否已拆分?
✓ 子消息是否设置 Gas 限制?
✓ 迁移是否使用惰性模式?
✓ 实例化是否避免预创建?
✓ 数值类型是否最小?
"""
7.9 完整合约模板
use cosmwasm_std::{
entry_point, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdResult, StdError, Addr, Uint128,
Order, to_binary, from_binary,
};
use cw_storage_plus::{Item, Map, Bound};
use serde::{Deserialize, Serialize};
// --- Gas 高效状态 ---
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Config { pub owner: Addr, pub paused: bool, pub fee_rate: u64 }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct UserCore { pub balance: Uint128, pub staked: Uint128, pub flags: u32 }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Position { pub token: String, pub amount: Uint128, pub entry_price: Uint128 }
pub const CONFIG: Item<Config> = Item::new("c");
pub const USER_COUNT: Item<u64> = Item::new("n");
pub const USER_CORE: Map<&Addr, UserCore> = Map::new("u");
pub const POSITIONS: Map<(&Addr, u64), Position> = Map::new("p");
pub const POSITION_COUNT: Map<&Addr, u64> = Map::new("pc");
// --- 消息 ---
#[cw_serde]
pub enum ExecuteMsg {
BatchTransfer { recipients: Vec<(String, Uint128)> },
DepositAndStake { amount: Uint128 },
}
#[cw_serde]
pub enum QueryMsg {
UserOverview { user: String },
UserPositions { user: String, start_after: Option<u64>, limit: Option<u32> },
}
// --- 实例化(惰性)---
#[entry_point]
pub fn instantiate(deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg) -> StdResult<Response> {
CONFIG.save(deps.storage, &Config { owner: deps.api.addr_validate(&msg.owner)?, paused: false, fee_rate: msg.fee_rate })?;
USER_COUNT.save(deps.storage, &0)?;
Ok(Response::new().add_attribute("chain", "msg-chain-1"))
}
// --- 执行(批量 + 复合)---
#[entry_point]
pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::BatchTransfer { recipients } => {
let sender = &info.sender;
let total: Uint128 = recipients.iter().map(|(_, a)| a).sum();
let mut core = USER_CORE.load(deps.storage, sender)?;
if core.balance < total { return Err(StdError::generic_err("Insufficient balance")); }
core.balance -= total;
USER_CORE.save(deps.storage, sender, &core)?;
for (addr_str, amount) in &recipients {
let r = deps.api.addr_validate(addr_str)?;
let mut rc = USER_CORE.load(deps.storage, &r).unwrap_or(UserCore { balance: Uint128::zero(), staked: Uint128::zero(), flags: 0 });
rc.balance += *amount;
USER_CORE.save(deps.storage, &r, &rc)?;
}
Ok(Response::new().add_attribute("action", "batch_transfer").add_attribute("count", recipients.len().to_string()))
}
ExecuteMsg::DepositAndStake { amount } => {
let user = &info.sender;
let mut core = USER_CORE.load(deps.storage, user)?;
core.balance += amount;
core.staked += amount;
USER_CORE.save(deps.storage, user, &core)?;
Ok(Response::new().add_attribute("action", "deposit_and_stake").add_attribute("amount", amount))
}
}
}
// --- 查询(聚合 + 分页)---
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::UserOverview { user } => {
let user = deps.api.addr_validate(&user)?;
let core = USER_CORE.load(deps.storage, &user).unwrap_or(UserCore { balance: Uint128::zero(), staked: Uint128::zero(), flags: 0 });
let count = POSITION_COUNT.load(deps.storage, &user).unwrap_or(0);
to_binary(&(core.balance, core.staked, core.flags, count))
}
QueryMsg::UserPositions { user, start_after, limit } => {
let user = deps.api.addr_validate(&user)?;
let limit = limit.unwrap_or(30).min(100) as usize;
let start = start_after.map(Bound::exclusive);
to_binary(&POSITIONS.prefix(&user).range(deps.storage, start, None, Order::Ascending).take(limit).collect::<StdResult<Vec<(u64, Position)>>>()?)
}
}
}
// --- 迁移(惰性)---
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> StdResult<Response> {
CONFIG.update(deps.storage, |mut c| { c.paused = false; Ok(c) })?;
Ok(Response::new().add_attribute("action", "migrate"))
}
7.10 常见 Gas 陷阱速查
// 陷阱 1: 循环内重复读取存储
// ❌ BAD
for user in users {
total += BALANCES.load(store, user)?; // N 次存储读取
}
// ✅ GOOD: 先 collect 再计算(但注意数据量)
let balances: Vec<_> = users.iter().map(|u| BALANCES.load(store, u)).collect::<StdResult<Vec<_>>>()?;
let total = balances.iter().sum();
// 陷阱 2: 不必要的序列化
// ❌ BAD: 读取完整大结构体仅改一个字段
let mut data = BIG_DATA.load(store, key)?;
data.small_field = new_value;
BIG_DATA.save(store, key, &data)?;
// ✅ GOOD: 将 small_field 独立存储
SMALL_FIELD.save(store, key, &new_value)?;
// 陷阱 3: unwrap 导致 panic → OOG
// ❌ BAD
let val = SOME_ITEM.load(store).unwrap();
// ✅ GOOD
let val = SOME_ITEM.may_load(store)?;
// 陷阱 4: 全表遍历不加限制
// ❌ BAD
for item in MAP.range(store, None, None, Order::Ascending) { ... }
// ✅ GOOD
for item in MAP.range(store, start, end, Order::Ascending).take(100) { ... }
// 陷阱 5: 子消息不设 Gas 限制
// ❌ BAD
SubMsg { msg, gas_limit: None, ... };
// ✅ GOOD
SubMsg { msg, gas_limit: Some(200_000), ... };
7.11 核心原则总结
1. 存储最贵 → 减少读写 = 直接降 Gas
2. 批量 > 逐条 → 单次消息处理 N 个操作
3. 惰性 > 即时 → 按需创建,按需迁移
4. 短 > 长 → 短键名、小类型、精简结构
5. 聚合 > 遍历 → 计数器替代全表扫描
6. 本地 > 外部 → 合约内计算优于跨合约调用
7. 预估 > 盲写 → 生成代码前估算 Gas
8. 监控 > 猜测 → 持续跟踪,优化热点路径
在 msg-chain-1 上,一次优化查询仅消耗 ~15,000 Gas (≈0.375 umsg),
一次未优化的全表扫描可达 5,000,000+ Gas (≈125 umsg),
相差超过 300 倍。AI Agent 的每次 Gas 优化,都是为用户节省真金白银。
文档版本: v1.0 | 目标链: msg-chain-1 | 基础代币: umsg
虚拟机: CosmWasm (WasmVM) | 适用: AI Agent / 合约开发者
