MSG Chain (CosmWasm) 智能合约可升级性与迁移指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
1. 概述
1.1 为什么在 CosmWasm 中需要合约升级
区块链上的智能合约一旦部署,默认是不可变的。然而,在实际开发中,合约升级的需求不可避免:
- 漏洞修复:发现安全漏洞时需紧急修补
- 功能迭代:添加新功能或优化现有逻辑
- 协议升级:底层链升级带来的兼容性调整
- 经济模型调整:费率、分配比例等参数变更
MSG Chain 作为基于 CosmWasm 的链,采用 msg 作为地址前缀(bech32),链 ID 为 msg-chain-1,为开发者提供了灵活的合约升级机制。
1.2 三种升级方式对比
CosmWasm 提供三种主要的合约升级路径,各有优劣:
| 方式 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
migrate 原生迁移 |
保留同一合约地址,状态完全继承,原生支持 | 需要原合约预留迁移权限,版本管理需自理 | 标准升级场景 |
| 代理模式 (Proxy) | 逻辑与状态分离,升级灵活,支持回退 | Gas 开销略高,存储布局需谨慎设计 | 需要频繁升级的场景 |
instantiate2 确定性地址 |
地址可预测,无需迁移权限 | 状态不继承,需手动迁移数据 | 全新部署场景 |
1.3 Migrate Entry Point 详解
migrate 是 CosmWasm 最核心的升级入口点。当管理员对合约地址发起 MsgMigrateContract 交易时,链会:
- 验证发起者是否为合约管理员(Contract Admin)
- 验证新 Code ID 是否有效
- 反序列化
MigrateMsg - 调用新代码的
migrate入口点 - 保留合约地址和状态不变
关键特性:
- 地址不变:升级后合约地址不变,所有引用无需更新
- 状态保留:原有的
Storage数据完全保留 - 管理员控制:只有合约管理员可触发迁移
- 可选状态清理:可在
migrate中读取并转换旧状态
// 合约管理员设置
// MsgUpdateAdmin 可在部署后更改管理员
// 如果管理员未设置,则合约不可迁移
1.4 Migrate 与 Proxy 的权衡决策
场景对比:
├── 小型合约 / 简单升级
│ └── ✅ 原生 migrate (最简单直接)
├── 大型系统 / 多合约协作
│ └── ✅ Proxy 模式 (升级灵活)
├── DAO 治理控制
│ └── ✅ 原生 migrate + DAO 作为管理员
└── 频繁升级 / 实验阶段
└── ✅ Proxy 模式 (零成本切换逻辑)
2. 原生迁移模式
2.1 基础 Migrate Entry Point
以下是 MSG Chain 上标准合约的完整迁移入口点实现:
use cosmwasm_std::{
entry_point, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env,
MessageInfo, Order, Response, StdError, StdResult, Storage,
};
use cw_storage_plus::{Item, Map};
use serde::{Deserialize, Serialize};
// ========== State ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Config {
pub owner: Addr,
pub version: String,
pub counter: u64,
pub paused: bool,
}
pub const CONFIG: Item<Config> = Item::new("config");
pub const USERS: Map<&Addr, u64> = Map::new("users");
// ========== Messages ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct InstantiateMsg {
pub owner: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Increment {},
Decrement {},
Reset { counter: u64 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetConfig {},
GetCounter {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct MigrateMsg {
pub version: String,
}
// ========== Instantiate ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
let config = Config {
owner: deps.api.addr_validate(&msg.owner)?,
version: "1.0.0".to_string(),
counter: 0,
paused: false,
};
CONFIG.save(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("owner", msg.owner)
.add_attribute("version", "1.0.0"))
}
// ========== Execute ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::Increment {} => execute_increment(deps),
ExecuteMsg::Decrement {} => execute_decrement(deps),
ExecuteMsg::Reset { counter } => execute_reset(deps, info, counter),
}
}
fn execute_increment(deps: DepsMut) -> StdResult<Response> {
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.counter = config.counter.wrapping_add(1);
Ok(config)
})?;
Ok(Response::new().add_attribute("method", "increment"))
}
fn execute_decrement(deps: DepsMut) -> StdResult<Response> {
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.counter = config.counter.wrapping_sub(1);
Ok(config)
})?;
Ok(Response::new().add_attribute("method", "decrement"))
}
fn execute_reset(
deps: DepsMut,
info: MessageInfo,
counter: u64,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(StdError::generic_err("Unauthorized"));
}
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.counter = counter;
Ok(config)
})?;
Ok(Response::new().add_attribute("method", "reset"))
}
// ========== Query ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetConfig {} => to_binary(&query_config(deps)?),
QueryMsg::GetCounter {} => to_binary(&query_counter(deps)?),
}
}
fn query_config(deps: Deps) -> StdResult<Config> {
CONFIG.load(deps.storage)
}
fn query_counter(deps: Deps) -> StdResult<u64> {
let config = CONFIG.load(deps.storage)?;
Ok(config.counter)
}
// ========== Migrate ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult<Response> {
// 1. 版本检查:防止重复迁移
let contract_info = deps.contract_info.clone();
if contract_info.version == msg.version {
return Err(StdError::generic_err(
"Already at target version, migration skipped",
));
}
// 2. 语义版本比较(如需要)
// 此处可集成 semver 库做严格版本比较
// 3. 加载并更新配置
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.version = msg.version;
Ok(config)
})?;
// 4. 日志记录迁移事件
Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("from_version", contract_info.version)
.add_attribute("to_version", msg.version))
}
2.2 ContractInfo 版本管理
CosmWasm 在 DepsMut 中提供 contract_info 字段,对应链上存储的 ContractInfo 结构:
pub struct ContractInfo {
pub code_id: u64,
pub creator: Addr,
pub admin: Option<Addr>,
pub label: String,
pub version: String,
pub ibc_port_id: Option<String>,
}
版本管理最佳实践:
// ========== 版本管理工具函数 ==========
const VERSION_KEY: &str = "contract_version";
/// 在 instantiate 中设置初始版本
pub fn set_initial_version(deps: &mut DepsMut, version: &str) -> StdResult<()> {
let mut info = deps.contract_info.clone();
info.version = version.to_string();
// 注意:contract_info 本身不可写,需要靠 migrate 入口更新
// 实际上 ContractInfo.version 只能通过 MsgMigrateContract 更新
// 这里只是一个记录方案
deps.storage.set(VERSION_KEY.as_bytes(), version.as_bytes());
Ok(())
}
/// 获取当前记录版本
pub fn get_recorded_version(storage: &dyn Storage) -> StdResult<String> {
let version_bytes = storage
.get(VERSION_KEY.as_bytes())
.unwrap_or_default();
if version_bytes.is_empty() {
return Ok("0.0.0".to_string());
}
Ok(String::from_utf8(version_bytes).unwrap_or_default())
}
/// 严格的版本迁移检查(使用 semver)
pub fn assert_valid_migration(
current: &str,
target: &str,
) -> StdResult<()> {
// 生产环境推荐使用 semver crate 做严格比较
// let current_ver = Version::from_str(current)?;
// let target_ver = Version::from_str(target)?;
// if target_ver <= current_ver {
// return Err(StdError::generic_err("Migration must increase version"));
// }
if current == target {
return Err(StdError::generic_err("Version unchanged"));
}
Ok(())
}
2.3 多版本迁移模式
当合约经历多个版本迭代时,需要设计可串联的迁移策略:
use cosmwasm_std::Storage;
// ========== 版本枚举 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum ContractVersion {
V1,
V2,
V3,
}
impl ContractVersion {
pub fn as_str(&self) -> &'static str {
match self {
ContractVersion::V1 => "1.0.0",
ContractVersion::V2 => "2.0.0",
ContractVersion::V3 => "3.0.0",
}
}
}
// ========== 阶梯迁移入口 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> {
let current_version = deps.contract_info.version.clone();
let target_version = msg.version.clone();
// 如果版本相同,跳过
if current_version == target_version {
return Err(StdError::generic_err("Already at target version"));
}
// 根据当前版本和目标版本选择迁移路径
match (current_version.as_str(), target_version.as_str()) {
("1.0.0", "2.0.0") => {
migrate_v1_to_v2(deps.storage)?;
}
("1.0.0", "3.0.0") => {
migrate_v1_to_v2(deps.storage)?;
migrate_v2_to_v3(deps.storage, &env)?;
}
("2.0.0", "3.0.0") => {
migrate_v2_to_v3(deps.storage, &env)?;
}
(from, to) => {
return Err(StdError::generic_err(format!(
"Unsupported migration path: {} -> {}",
from, to
)));
}
}
// 更新版本记录
deps.storage.set(
VERSION_KEY.as_bytes(),
target_version.as_bytes(),
);
Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("from", ¤t_version)
.add_attribute("to", &target_version))
}
// ========== 各版本迁移函数 ==========
fn migrate_v1_to_v2(storage: &mut dyn Storage) -> StdResult<()> {
// 1. 读取 V1 状态
// 2. 转换为 V2 格式
// 3. 清理旧 key
Ok(())
}
fn migrate_v2_to_v3(storage: &mut dyn Storage, _env: &Env) -> StdResult<()> {
// 跨版本数据迁移
Ok(())
}
2.4 MigrateMsg 设计模式
// ========== 增强型 MigrateMsg ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum MigrateMsg {
/// 升级到指定版本(无参数迁移)
Upgrade {
version: String,
},
/// 升级并重新配置
UpgradeWithConfig {
version: String,
new_owner: Option<String>,
params: Option<MigrationParams>,
},
/// 紧急暂停
EmergencyPause {
reason: String,
},
/// 数据迁移(仅状态迁移,不改变代码)
MigrateData {
target_version: String,
migrate_all_users: bool,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct MigrationParams {
pub new_counter_limit: Option<u64>,
pub fee_rate: Option<u64>,
pub whitelist_only: Option<bool>,
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> {
match msg {
MigrateMsg::Upgrade { version } => {
handle_upgrade(deps, env, version)
}
MigrateMsg::UpgradeWithConfig {
version,
new_owner,
params,
} => handle_upgrade_with_config(deps, env, version, new_owner, params),
MigrateMsg::EmergencyPause { reason } => {
handle_emergency_pause(deps, env, reason)
}
MigrateMsg::MigrateData {
target_version,
migrate_all_users,
} => handle_migrate_data(deps, env, target_version, migrate_all_users),
}
}
fn handle_upgrade(deps: DepsMut, _env: Env, version: String) -> StdResult<Response> {
let current = deps.contract_info.version.clone();
if current == version {
return Err(StdError::generic_err("Already at target version"));
}
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.version = version.clone();
Ok(config)
})?;
Ok(Response::new()
.add_attribute("method", "upgrade")
.add_attribute("from", current)
.add_attribute("to", version))
}
fn handle_upgrade_with_config(
deps: DepsMut,
_env: Env,
version: String,
new_owner: Option<String>,
params: Option<MigrationParams>,
) -> StdResult<Response> {
let current = deps.contract_info.version.clone();
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.version = version.clone();
if let Some(ref owner) = new_owner {
config.owner = deps.api.addr_validate(owner)?;
}
Ok(config)
})?;
let mut resp = Response::new()
.add_attribute("method", "upgrade_with_config")
.add_attribute("from", current)
.add_attribute("to", version);
if let Some(p) = params {
if let Some(limit) = p.new_counter_limit {
// 应用新参数
resp = resp.add_attribute("new_counter_limit", limit.to_string());
}
}
Ok(resp)
}
fn handle_emergency_pause(
deps: DepsMut,
_env: Env,
reason: String,
) -> StdResult<Response> {
CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.paused = true;
Ok(config)
})?;
Ok(Response::new()
.add_attribute("method", "emergency_pause")
.add_attribute("reason", reason))
}
fn handle_migrate_data(
_deps: DepsMut,
_env: Env,
_target_version: String,
_migrate_all_users: bool,
) -> StdResult<Response> {
// 批量迁移用户数据
Ok(Response::new()
.add_attribute("method", "migrate_data"))
}
2.5 迁移测试套件
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_dependencies_with_balance, mock_env,
mock_info, MOCK_CONTRACT_ADDR,
};
use cosmwasm_std::{coins, from_binary, ContractInfo};
// ========== 辅助函数 ==========
fn create_contract_info(version: &str) -> ContractInfo {
ContractInfo {
code_id: 1,
creator: Addr::unchecked("msg1creator00000000000000000000000000000000"),
admin: Some(Addr::unchecked("msg1admin000000000000000000000000000000000")),
label: "test_contract".to_string(),
version: version.to_string(),
ibc_port_id: None,
}
}
fn setup_contract() -> (
cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>,
Env,
) {
let mut deps = mock_dependencies();
let env = mock_env();
let info = mock_info("msg1sender000000000000000000000000000000000", &[]);
// 设置初始合约信息
deps.contract_info = create_contract_info("1.0.0");
let msg = InstantiateMsg {
owner: "msg1owner000000000000000000000000000000000".to_string(),
};
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(res.attributes[0].value, "instantiate");
(deps, env)
}
// ========== 测试基础迁移 ==========
#[test]
fn test_basic_migration() {
let (mut deps, env) = setup_contract();
// 验证初始版本
assert_eq!(deps.contract_info.version, "1.0.0");
// 执行迁移到 2.0.0
let migrate_msg = MigrateMsg {
version: "2.0.0".to_string(),
};
// 模拟合约信息更新
deps.contract_info.version = "2.0.0".to_string();
let res = migrate(deps.as_mut(), env, migrate_msg).unwrap();
// 验证迁移结果
assert_eq!(res.attributes[1].value, "migrate");
assert_eq!(res.attributes[2].value, "1.0.0");
// 验证状态中版本已更新
let config = CONFIG.load(&deps.storage).unwrap();
assert_eq!(config.version, "2.0.0");
}
// ========== 测试重复迁移拒绝 ==========
#[test]
fn test_reject_duplicate_migration() {
let (mut deps, env) = setup_contract();
// 模拟已经是 2.0.0
deps.contract_info.version = "2.0.0".to_string();
// 尝试再次迁移到相同版本
let migrate_msg = MigrateMsg {
version: "2.0.0".to_string(),
};
let err = migrate(deps.as_mut(), env.clone(), migrate_msg).unwrap_err();
assert_eq!(err.to_string(), "Generic error: Already at target version, migration skipped");
// 恢复原始版本再尝试
deps.contract_info.version = "1.0.0".to_string();
let migrate_msg = MigrateMsg {
version: "3.0.0".to_string(),
};
let res = migrate(deps.as_mut(), env, migrate_msg).unwrap();
assert_eq!(res.attributes[3].value, "3.0.0");
}
// ========== 测试迁移后功能完整性 ==========
#[test]
fn test_functionality_after_migration() {
let (mut deps, env) = setup_contract();
// 迁移到 2.0.0
deps.contract_info.version = "2.0.0".to_string();
let migrate_msg = MigrateMsg {
version: "2.0.0".to_string(),
};
migrate(deps.as_mut(), env.clone(), migrate_msg).unwrap();
// 迁移后合约功能仍然正常
let exec_msg = ExecuteMsg::Increment {};
let info = mock_info("msg1user00000000000000000000000000000000000", &[]);
let res = execute(deps.as_mut(), env.clone(), info, exec_msg).unwrap();
assert_eq!(res.attributes[0].value, "increment");
// 验证计数增加
let config = CONFIG.load(&deps.storage).unwrap();
assert_eq!(config.counter, 1);
}
// ========== 测试回滚迁移 ==========
#[test]
fn test_migration_state_integrity() {
let (mut deps, env) = setup_contract();
// 先做一些操作
let exec_msg = ExecuteMsg::Increment {};
let info = mock_info("msg1user00000000000000000000000000000000000", &[]);
execute(deps.as_mut(), env.clone(), info, exec_msg).unwrap();
// 确认状态已改变
let config_before = CONFIG.load(&deps.storage).unwrap();
assert_eq!(config_before.counter, 1);
// 执行迁移
deps.contract_info.version = "2.0.0".to_string();
let migrate_msg = MigrateMsg {
version: "2.0.0".to_string(),
};
let res = migrate(deps.as_mut(), env.clone(), migrate_msg).unwrap();
// 迁移后状态应保持(计数器不变)
let config_after = CONFIG.load(&deps.storage).unwrap();
assert_eq!(config_after.counter, 1);
assert_eq!(config_after.version, "2.0.0");
}
// ========== 测试批量迁移性能 ==========
#[test]
fn test_high_volume_state_migration() {
let mut deps = mock_dependencies();
let env = mock_env();
// 模拟大量用户数据
let info = mock_info("msg1admin000000000000000000000000000000000", &[]);
let msg = InstantiateMsg {
owner: "msg1owner000000000000000000000000000000000".to_string(),
};
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
// 添加大量用户
for i in 0..1000u64 {
let addr = Addr::unchecked(format!("msg1user{:032x}", i));
USERS.save(deps.as_mut().storage, &addr, &i).unwrap();
}
// 执行迁移
deps.contract_info.version = "2.0.0".to_string();
let migrate_msg = MigrateMsg {
version: "2.0.0".to_string(),
};
let res = migrate(deps.as_mut(), env, migrate_msg).unwrap();
assert_eq!(res.attributes[0].value, "migrate");
// 验证用户数据完整
for i in 0..1000u64 {
let addr = Addr::unchecked(format!("msg1user{:032x}", i));
let val = USERS.load(&deps.storage, &addr).unwrap();
assert_eq!(val, i);
}
}
}
2.6 完整合约代码
pub mod contract;
pub mod msg;
pub mod state;
#[cfg(test)]
mod testing;
3. 代理模式
3.1 Transparent Proxy 原理
代理模式(Proxy Pattern)将逻辑与状态分离到两个合约中:
- Proxy 合约:持有状态(Storage),通过
delegatecall或 CosmWasm 的wasm消息将执行委托给逻辑合约 - Implementation 合约:包含业务逻辑,不保留状态
在 CosmWasm 中,由于没有原生的 delegatecall,我们通过自定义实现达到类似效果:
用户
│
▼
Proxy 合约 (msg1proxy...) ←── 持有状态
│
│ query: 直接从自身 storage 读取
│ execute: 调用实现合约,实现合约通过 query 读取 proxy 的状态
│ wasm: 实现合约通过子消息返回结果
▼
Implementation 合约 (msg1impl...) ←── 仅有逻辑代码
3.2 Proxy 合约核心实现
use cosmwasm_std::{
entry_point, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env,
MessageInfo, Reply, Response, StdError, StdResult, SubMsg, WasmMsg,
WasmQuery,
};
use cw_storage_plus::{Item, Map};
use serde::{Deserialize, Serialize};
// ========== Proxy 状态 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ProxyState {
pub implementation: Addr,
pub admin: Addr,
pub paused: bool,
}
pub const PROXY_STATE: Item<ProxyState> = Item::new("proxy_state");
// ========== Proxy 消息 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ProxyExecuteMsg {
/// 升级实现合约
Upgrade {
new_implementation: String,
},
/// 转移管理员权限
TransferAdmin {
new_admin: String,
},
/// 紧急暂停
Pause {},
/// 取消暂停
Unpause {},
/// 转发给实现合约的具体消息
Execute {
msg: Binary,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ProxyQueryMsg {
/// 获取实现合约地址
GetImplementation {},
/// 转发给实现合约的查询
Query {
msg: Binary,
},
}
// ========== Proxy Instantiate ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ProxyInstantiateMsg,
) -> StdResult<Response> {
let state = ProxyState {
implementation: deps.api.addr_validate(&msg.implementation)?,
admin: info.sender.clone(),
paused: false,
};
PROXY_STATE.save(deps.storage, &state)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("implementation", &msg.implementation)
.add_attribute("admin", info.sender))
}
// ========== Proxy Execute ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ProxyExecuteMsg,
) -> StdResult<Response> {
match msg {
ProxyExecuteMsg::Upgrade {
new_implementation,
} => execute_upgrade(deps, env, info, new_implementation),
ProxyExecuteMsg::TransferAdmin { new_admin } => {
execute_transfer_admin(deps, env, info, new_admin)
}
ProxyExecuteMsg::Pause {} => execute_pause(deps, env, info),
ProxyExecuteMsg::Unpause {} => execute_unpause(deps, env, info),
ProxyExecuteMsg::Execute { msg } => {
execute_forward(deps, env, info, msg)
}
}
}
/// 升级实现合约地址
fn execute_upgrade(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_implementation: String,
) -> StdResult<Response> {
let state = PROXY_STATE.load(deps.storage)?;
// 仅管理员可升级
if info.sender != state.admin {
return Err(StdError::generic_err("Unauthorized: only admin can upgrade"));
}
let new_addr = deps.api.addr_validate(&new_implementation)?;
PROXY_STATE.update(deps.storage, |mut state| -> StdResult<_> {
let old = state.implementation.clone();
state.implementation = new_addr;
Ok(state)
})?;
Ok(Response::new()
.add_attribute("method", "upgrade")
.add_attribute("from", state.implementation.to_string())
.add_attribute("to", new_implementation))
}
/// 转移管理员权限
fn execute_transfer_admin(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_admin: String,
) -> StdResult<Response> {
let state = PROXY_STATE.load(deps.storage)?;
if info.sender != state.admin {
return Err(StdError::generic_err("Unauthorized: only admin can transfer"));
}
let new_admin_addr = deps.api.addr_validate(&new_admin)?;
PROXY_STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.admin = new_admin_addr;
Ok(state)
})?;
Ok(Response::new()
.add_attribute("method", "transfer_admin")
.add_attribute("new_admin", new_admin))
}
fn execute_pause(
deps: DepsMut,
_env: Env,
info: MessageInfo,
) -> StdResult<Response> {
let state = PROXY_STATE.load(deps.storage)?;
if info.sender != state.admin {
return Err(StdError::generic_err("Unauthorized"));
}
PROXY_STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.paused = true;
Ok(state)
})?;
Ok(Response::new().add_attribute("method", "pause"))
}
fn execute_unpause(
deps: DepsMut,
_env: Env,
info: MessageInfo,
) -> StdResult<Response> {
let state = PROXY_STATE.load(deps.storage)?;
if info.sender != state.admin {
return Err(StdError::generic_err("Unauthorized"));
}
PROXY_STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.paused = false;
Ok(state)
})?;
Ok(Response::new().add_attribute("method", "unpause"))
}
/// 转发执行到实现合约
fn execute_forward(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: Binary,
) -> StdResult<Response> {
let state = PROXY_STATE.load(deps.storage)?;
if state.paused {
return Err(StdError::generic_err("Contract is paused"));
}
// 构造 WasmMsg::Execute 调用实现合约
// 实现合约会读取 proxy 的状态(通过 Query 或共享存储)
let execute_msg = WasmMsg::Execute {
contract_addr: state.implementation.to_string(),
msg: msg,
funds: vec![],
};
Ok(Response::new()
.add_submessage(SubMsg::new(execute_msg))
.add_attribute("method", "proxy_execute")
.add_attribute("implementation", state.implementation))
}
// ========== Proxy Query ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: ProxyQueryMsg) -> StdResult<Binary> {
match msg {
ProxyQueryMsg::GetImplementation {} => {
let state = PROXY_STATE.load(deps.storage)?;
to_binary(&state.implementation)
}
ProxyQueryMsg::Query { msg } => query_forward(deps, msg),
}
}
/// 转发查询到实现合约
fn query_forward(deps: Deps, msg: Binary) -> StdResult<Binary> {
let state = PROXY_STATE.load(deps.storage)?;
// 通过 WasmQuery::Smart 查询实现合约
let query = WasmQuery::Smart {
contract_addr: state.implementation.to_string(),
msg: msg,
};
// 注意:此处需要从 deps.querier 查询
// 但由于 querier 在 query 中可用,我们直接构造查询
deps.querier.query(&query.into())
}
// ========== Proxy 声明 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ProxyInstantiateMsg {
pub implementation: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ProxyMigrateMsg {
UpgradeImplementation { new_implementation: String },
}
3.3 存储冲突避免策略
在代理模式中,最大的挑战是存储布局冲突。Proxy 合约和 Implementation 合约使用相同的存储空间(因为状态在 Proxy 合约中),因此必须确保两者的存储 key 不冲突。
// ========== 存储 Key 命名规范 ==========
/// Proxy 合约使用特定前缀
pub const PROXY_KEY_PREFIX: &str = "proxy_";
/// 实现合约使用独立前缀
pub const IMPL_KEY_PREFIX: &str = "impl_";
// ========== 防止冲突的存储工具 ==========
/// 为存储 key 添加前缀以防止冲突
pub struct NamespacedStorage<'a> {
storage: &'a dyn Storage,
namespace: &'a [u8],
}
impl<'a> NamespacedStorage<'a> {
pub fn new(storage: &'a dyn Storage, namespace: &'a [u8]) -> Self {
Self { storage, namespace }
}
}
impl Storage for NamespacedStorage<'_> {
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
let prefixed = [self.namespace, key].concat();
self.storage.get(&prefixed)
}
fn set(&mut self, key: &[u8], value: &[u8]) {
let prefixed = [self.namespace, key].concat();
self.storage.set(&prefixed, value);
}
fn remove(&mut self, key: &[u8]) {
let prefixed = [self.namespace, key].concat();
self.storage.remove(&prefixed);
}
fn scan<'b>(
&'b self,
start: Option<&[u8]>,
end: Option<&[u8]>,
order: cosmwasm_std::Order,
) -> Box<dyn Iterator<Item = Vec<u8>> + 'b> {
let prefixed_start = start.map(|s| [self.namespace, s].concat());
let prefixed_end = end.map(|e| [self.namespace, e].concat());
self.storage
.scan(prefixed_start.as_deref(), prefixed_end.as_deref(), order)
}
}
// ========== 推荐方案:独立存储空间 ==========
/// 更好的方案:实现合约通过自己的 Storage trait 操作,
/// Proxy 合约通过 WasmQuery 与实现合约通信
///
/// 这种方式完全避免了存储冲突,但增加了 Gas 开销
///
/// 架构:
/// Proxy: 状态存储层 + 路由层
/// Impl V1: 逻辑层 (独立存储)
/// Impl V2: 逻辑层 (独立存储)
///
/// 升级时:
/// Proxy 更新 implementation 地址指向新实现
/// 新实现可以读取旧实现的状态(通过 Query)
/// 或从 Proxy 的存储中读取
3.4 完整代理模式测试
#[cfg(test)]
mod proxy_tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_dependencies_with_balance, mock_env,
mock_info, MOCK_CONTRACT_ADDR,
};
const PROXY_ADMIN: &str = "msg1admin000000000000000000000000000000000";
const IMPL_V1: &str = "msg1impl11111111111111111111111111111111111";
const IMPL_V2: &str = "msg1impl22222222222222222222222222222222222";
fn setup_proxy() -> (
cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>,
Env,
) {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = ProxyInstantiateMsg {
implementation: IMPL_V1.to_string(),
};
let info = mock_info(PROXY_ADMIN, &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(res.attributes[0].value, "instantiate");
(deps, env)
}
// ========== 测试代理初始化 ==========
#[test]
fn test_proxy_initialization() {
let (deps, _env) = setup_proxy();
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert_eq!(state.implementation, Addr::unchecked(IMPL_V1));
assert_eq!(state.admin, Addr::unchecked(PROXY_ADMIN));
assert!(!state.paused);
}
// ========== 测试合约升级 ==========
#[test]
fn test_upgrade_implementation() {
let (mut deps, env) = setup_proxy();
// 管理员升级合约
let info = mock_info(PROXY_ADMIN, &[]);
let msg = ProxyExecuteMsg::Upgrade {
new_implementation: IMPL_V2.to_string(),
};
let res = execute(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.attributes[0].value, "upgrade");
assert_eq!(res.attributes[1].value, IMPL_V1);
assert_eq!(res.attributes[2].value, IMPL_V2);
// 验证状态已更新
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert_eq!(state.implementation, Addr::unchecked(IMPL_V2));
}
// ========== 测试非管理员无法升级 ==========
#[test]
fn test_non_admin_cannot_upgrade() {
let (mut deps, env) = setup_proxy();
let info = mock_info("msg1attacker0000000000000000000000000000000", &[]);
let msg = ProxyExecuteMsg::Upgrade {
new_implementation: IMPL_V2.to_string(),
};
let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
assert!(err.to_string().contains("Unauthorized"));
}
// ========== 测试管理员转移 ==========
#[test]
fn test_transfer_admin() {
let (mut deps, env) = setup_proxy();
let new_admin = "msg1newadmin0000000000000000000000000000000";
let info = mock_info(PROXY_ADMIN, &[]);
let msg = ProxyExecuteMsg::TransferAdmin {
new_admin: new_admin.to_string(),
};
let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(res.attributes[0].value, "transfer_admin");
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert_eq!(state.admin, Addr::unchecked(new_admin));
// 旧管理员不能再操作
let old_info = mock_info(PROXY_ADMIN, &[]);
let upgrade_msg = ProxyExecuteMsg::Upgrade {
new_implementation: IMPL_V2.to_string(),
};
let err = execute(
deps.as_mut(),
env.clone(),
old_info,
upgrade_msg,
)
.unwrap_err();
assert!(err.to_string().contains("Unauthorized"));
}
// ========== 测试暂停机制 ==========
#[test]
fn test_pause_unpause() {
let (mut deps, env) = setup_proxy();
// 管理员暂停
let info = mock_info(PROXY_ADMIN, &[]);
let msg = ProxyExecuteMsg::Pause {};
execute(deps.as_mut(), env.clone(), info, msg).unwrap();
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert!(state.paused);
// 暂停后操作应被拒绝
let exec_info = mock_info("msg1user00000000000000000000000000000000000", &[]);
let exec_msg = ProxyExecuteMsg::Execute {
msg: to_binary(&"dummy").unwrap(),
};
let err = execute(
deps.as_mut(),
env.clone(),
exec_info,
exec_msg,
)
.unwrap_err();
assert!(err.to_string().contains("paused"));
// 管理员取消暂停
let admin_info = mock_info(PROXY_ADMIN, &[]);
let unpause_msg = ProxyExecuteMsg::Unpause {};
execute(deps.as_mut(), env, admin_info, unpause_msg).unwrap();
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert!(!state.paused);
}
// ========== 测试转发执行 ==========
#[test]
fn test_forward_execution() {
let (mut deps, env) = setup_proxy();
let info = mock_info("msg1user00000000000000000000000000000000000", &[]);
let exec_msg = ProxyExecuteMsg::Execute {
msg: to_binary(&"increment").unwrap(),
};
let res = execute(deps.as_mut(), env, info, exec_msg).unwrap();
// 验证产生了转发到实现合约的子消息
assert_eq!(res.messages.len(), 1);
match &res.messages[0].msg {
cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
..
}) => {
assert_eq!(contract_addr, IMPL_V1);
}
_ => panic!("Expected WasmMsg::Execute"),
}
}
// ========== 测试查询实现合约地址 ==========
#[test]
fn test_query_implementation() {
let (deps, env) = setup_proxy();
let msg = ProxyQueryMsg::GetImplementation {};
let bin = query(deps.as_ref(), env, msg).unwrap();
let impl_addr: Addr = from_binary(&bin).unwrap();
assert_eq!(impl_addr, Addr::unchecked(IMPL_V1));
}
// ========== 测试连续升级 ==========
#[test]
fn test_multiple_upgrades() {
let (mut deps, env) = setup_proxy();
let impl_v3 = "msg1impl33333333333333333333333333333333333";
// 第一次升级
let info = mock_info(PROXY_ADMIN, &[]);
let msg = ProxyExecuteMsg::Upgrade {
new_implementation: IMPL_V2.to_string(),
};
execute(deps.as_mut(), env.clone(), info, msg).unwrap();
// 第二次升级
let info = mock_info(PROXY_ADMIN, &[]);
let msg = ProxyExecuteMsg::Upgrade {
new_implementation: impl_v3.to_string(),
};
execute(deps.as_mut(), env, info, msg).unwrap();
let state = PROXY_STATE.load(&deps.storage).unwrap();
assert_eq!(state.implementation, Addr::unchecked(impl_v3));
}
}
// ========== 实现合约示例 ==========
/// 这是一个与 Proxy 配合使用的示例实现合约
/// 它不直接存储状态,而是通过 Query 与 Proxy 通信
mod implementation_contract {
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdResult,
};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ImplInstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ImplExecuteMsg {
Increment {},
Decrement {},
Reset { counter: u64 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ImplQueryMsg {
GetCounter {},
GetInfo {},
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: ImplInstantiateMsg,
) -> StdResult<Response> {
Ok(Response::new())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: ImplExecuteMsg,
) -> StdResult<Response> {
match msg {
ImplExecuteMsg::Increment {} => {
// 实现实际业务逻辑
// 注意:此处不能直接使用 deps.storage
// 因为状态存储在 Proxy 合约中
Ok(Response::new().add_attribute("impl", "increment"))
}
ImplExecuteMsg::Decrement {} => {
Ok(Response::new().add_attribute("impl", "decrement"))
}
ImplExecuteMsg::Reset { counter } => {
Ok(Response::new()
.add_attribute("impl", "reset")
.add_attribute("counter", counter.to_string()))
}
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(_deps: Deps, _env: Env, msg: ImplQueryMsg) -> StdResult<Binary> {
match msg {
ImplQueryMsg::GetCounter {} => {
to_binary(&0u64)
}
ImplQueryMsg::GetInfo {} => {
to_binary(&"implementation_v1")
}
}
}
}
3.5 透明代理 vs 通用代理
// ========== 透明代理(Transparent Proxy) ==========
//
// 工作原理:
// - 如果调用者是管理员,则执行代理管理操作(升级、暂停等)
// - 如果调用者是普通用户,则转发到实现合约
//
// 优点:普通用户完全无感,不需要知道 proxy 的存在
// 缺点:管理员的每次调用都有 Gas 开销判断
// ========== UUPS 代理(Universal Upgradeable Proxy) ==========
//
// 工作原理:
// - 升级逻辑在实现合约中,而不是在代理中
// - 代理仅负责 delegate 调用
//
// 优点:代理合约极小(减少 Gas)
// 缺点:需要在实现合约中编写升级逻辑
// ========== MSG Chain 推荐方案 ==========
/// 推荐使用透明代理模式,原因:
/// 1. 升级逻辑集中在 Proxy 中,审计更容易
/// 2. 实现合约更纯粹,专注于业务
/// 3. 符合 CosmWasm 的设计哲学
///
/// ```
/// msg 链上合约架构:
///
/// ┌─────────────┐
/// │ 用户 │
/// └──────┬──────┘
/// │
/// ┌──────▼──────┐
/// │ Proxy │ ← msg1proxy...
/// │ - 状态存储 │
/// │ - 访问控制 │
/// │ - 升级管理 │
/// └──────┬──────┘
/// │
/// ┌──────▼──────┐
/// │ Impl V2 │ ← msg1implv2...
/// │ - 业务逻辑 │
/// │ - 无状态 │
/// └─────────────┘
/// ```
4. 工厂升级模式
4.1 基于 Code ID 的工厂模式
工厂模式适用于需要创建多个同类型合约实例的场景(如每个用户一个合约实例):
use cosmwasm_std::{
entry_point, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env,
MessageInfo, Order, Reply, Response, StdError, StdResult, SubMsg,
SubMsgResult, WasmMsg,
};
use cw_storage_plus::{Item, Map};
use cw_utils::parse_reply_instantiate_data;
use serde::{Deserialize, Serialize};
// ========== 工厂状态 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct FactoryConfig {
pub admin: Addr,
pub current_code_id: u64,
pub version: String,
pub paused: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ChildContract {
pub address: Addr,
pub code_id: u64,
pub version: String,
pub created_at: u64,
pub owner: Addr,
}
pub const FACTORY_CONFIG: Item<FactoryConfig> = Item::new("factory_config");
pub const CHILD_CONTRACTS: Map<&Addr, ChildContract> = Map::new("child_contracts");
// ========== 工厂消息 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct FactoryInstantiateMsg {
pub admin: String,
pub initial_code_id: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FactoryExecuteMsg {
/// 创建新的子合约实例
CreateChild {
label: String,
owner: String,
init_msg: Binary,
},
/// 升级工厂使用的 Code ID(新建实例使用新代码)
UpgradeCodeId {
new_code_id: u64,
version: String,
},
/// 批量迁移已有子合约到新 Code ID
MigrateChildren {
child_addresses: Vec<String>,
migrate_msg: Binary,
},
/// 转移管理员权限
TransferAdmin {
new_admin: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FactoryQueryMsg {
GetConfig {},
ListChildren {
start_after: Option<String>,
limit: Option<u32>,
},
GetChild {
address: String,
},
}
pub const CREATE_CHILD_REPLY_ID: u64 = 1;
// ========== 工厂实例化 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: FactoryInstantiateMsg,
) -> StdResult<Response> {
let config = FactoryConfig {
admin: deps.api.addr_validate(&msg.admin)?,
current_code_id: msg.initial_code_id,
version: "1.0.0".to_string(),
paused: false,
};
FACTORY_CONFIG.save(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("admin", &msg.admin)
.add_attribute("initial_code_id", msg.initial_code_id.to_string()))
}
// ========== 工厂执行 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: FactoryExecuteMsg,
) -> StdResult<Response> {
match msg {
FactoryExecuteMsg::CreateChild {
label,
owner,
init_msg,
} => execute_create_child(deps, env, info, label, owner, init_msg),
FactoryExecuteMsg::UpgradeCodeId {
new_code_id,
version,
} => execute_upgrade_code_id(deps, env, info, new_code_id, version),
FactoryExecuteMsg::MigrateChildren {
child_addresses,
migrate_msg,
} => execute_migrate_children(deps, env, info, child_addresses, migrate_msg),
FactoryExecuteMsg::TransferAdmin { new_admin } => {
execute_transfer_admin(deps, env, info, new_admin)
}
}
}
/// 创建子合约(使用 Reply 模式获取确定性地址)
fn execute_create_child(
deps: DepsMut,
_env: Env,
info: MessageInfo,
label: String,
owner: String,
init_msg: Binary,
) -> StdResult<Response> {
let config = FACTORY_CONFIG.load(deps.storage)?;
let owner_addr = deps.api.addr_validate(&owner)?;
// 构造 WasmMsg::Instantiate
let instantiate_msg = WasmMsg::Instantiate {
admin: Some(config.admin.to_string()),
code_id: config.current_code_id,
msg: init_msg,
funds: vec![],
label: label.clone(),
};
// 使用 SubMsg 包裹以获取 Reply 中的合约地址
let sub_msg = SubMsg::reply_on_success(instantiate_msg, CREATE_CHILD_REPLY_ID);
Ok(Response::new()
.add_submessage(sub_msg)
.add_attribute("method", "create_child")
.add_attribute("owner", owner_addr)
.add_attribute("label", label))
}
/// 升级 Code ID(新实例将使用新代码)
fn execute_upgrade_code_id(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_code_id: u64,
version: String,
) -> StdResult<Response> {
let config = FACTORY_CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
FACTORY_CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.current_code_id = new_code_id;
config.version = version.clone();
Ok(config)
})?;
Ok(Response::new()
.add_attribute("method", "upgrade_code_id")
.add_attribute("old_code_id", config.current_code_id.to_string())
.add_attribute("new_code_id", new_code_id.to_string())
.add_attribute("version", version))
}
/// 批量迁移子合约到新代码
fn execute_migrate_children(
deps: DepsMut,
_env: Env,
info: MessageInfo,
child_addresses: Vec<String>,
migrate_msg: Binary,
) -> StdResult<Response> {
let config = FACTORY_CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
let mut response = Response::new()
.add_attribute("method", "migrate_children")
.add_attribute("count", child_addresses.len().to_string());
for addr_str in child_addresses {
let addr = deps.api.addr_validate(&addr_str)?;
// 验证子合约是否由该工厂创建
let child = CHILD_CONTRACTS.load(deps.storage, &addr)?;
// 构造迁移消息
let migrate = WasmMsg::Migrate {
contract_addr: addr.to_string(),
new_code_id: config.current_code_id,
msg: migrate_msg.clone(),
};
response = response.add_message(migrate);
// 更新子合约记录
CHILD_CONTRACTS.update(deps.storage, &addr, |mut c| -> StdResult<_> {
c.code_id = config.current_code_id;
c.version = config.version.clone();
Ok(c)
})?;
}
Ok(response)
}
fn execute_transfer_admin(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_admin: String,
) -> StdResult<Response> {
let config = FACTORY_CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
let new_admin_addr = deps.api.addr_validate(&new_admin)?;
FACTORY_CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.admin = new_admin_addr;
Ok(config)
})?;
Ok(Response::new()
.add_attribute("method", "transfer_admin")
.add_attribute("new_admin", new_admin))
}
// ========== 工厂 Reply 处理 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
CREATE_CHILD_REPLY_ID => handle_create_child_reply(deps, msg),
id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))),
}
}
fn handle_create_child_reply(
deps: DepsMut,
msg: Reply,
) -> StdResult<Response> {
let res = parse_reply_instantiate_data(msg, true)?;
let child_addr = deps.api.addr_validate(&res.contract_address)?;
// 从 instantiate 消息中提取 owner
// 注意:真实场景中你可能需要将 owner 信息存储在 reply 数据中
let child_contract = ChildContract {
address: child_addr.clone(),
code_id: 1, // 从工厂配置读取
version: "1.0.0".to_string(),
created_at: 0, // 从 env 获取
owner: Addr::unchecked(""), // 从消息中解析
};
CHILD_CONTRACTS.save(deps.storage, &child_addr, &child_contract)?;
Ok(Response::new()
.add_attribute("method", "child_created")
.add_attribute("child_address", child_addr))
}
// ========== 工厂查询 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: FactoryQueryMsg) -> StdResult<Binary> {
match msg {
FactoryQueryMsg::GetConfig {} => {
let config = FACTORY_CONFIG.load(deps.storage)?;
to_binary(&config)
}
FactoryQueryMsg::ListChildren {
start_after,
limit,
} => {
let start = start_after
.map(|s| deps.api.addr_validate(&s))
.transpose()?;
let limit = limit.unwrap_or(30).min(100);
let children: Vec<ChildContract> = CHILD_CONTRACTS
.range(deps.storage, start.as_ref(), None, Order::Ascending)
.take(limit as usize)
.map(|item| {
let (_, child) = item?;
Ok(child)
})
.collect::<StdResult<_>>()?;
to_binary(&children)
}
FactoryQueryMsg::GetChild { address } => {
let addr = deps.api.addr_validate(&address)?;
let child = CHILD_CONTRACTS.load(deps.storage, &addr)?;
to_binary(&child)
}
}
}
4.2 工厂升级测试
#[cfg(test)]
mod factory_tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_dependencies_with_balance, mock_env,
mock_info, MOCK_CONTRACT_ADDR,
};
use cosmwasm_std::SubMsgResult;
const FACTORY_ADMIN: &str = "msg1admin000000000000000000000000000000000";
fn setup_factory() -> (
cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>,
Env,
) {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = FactoryInstantiateMsg {
admin: FACTORY_ADMIN.to_string(),
initial_code_id: 1,
};
let info = mock_info(FACTORY_ADMIN, &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(res.attributes[0].value, "instantiate");
(deps, env)
}
// ========== 测试工厂初始化 ==========
#[test]
fn test_factory_initialization() {
let (deps, _env) = setup_factory();
let config = FACTORY_CONFIG.load(&deps.storage).unwrap();
assert_eq!(config.admin, Addr::unchecked(FACTORY_ADMIN));
assert_eq!(config.current_code_id, 1);
assert_eq!(config.version, "1.0.0");
}
// ========== 测试 Upgrade Code ID ==========
#[test]
fn test_upgrade_code_id() {
let (mut deps, env) = setup_factory();
let info = mock_info(FACTORY_ADMIN, &[]);
let msg = FactoryExecuteMsg::UpgradeCodeId {
new_code_id: 2,
version: "2.0.0".to_string(),
};
let res = execute(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.attributes[0].value, "upgrade_code_id");
let config = FACTORY_CONFIG.load(&deps.storage).unwrap();
assert_eq!(config.current_code_id, 2);
assert_eq!(config.version, "2.0.0");
}
// ========== 测试非管理员无法升级 ==========
#[test]
fn test_non_admin_cannot_upgrade_factory() {
let (mut deps, env) = setup_factory();
let info = mock_info("msg1attacker0000000000000000000000000000000", &[]);
let msg = FactoryExecuteMsg::UpgradeCodeId {
new_code_id: 2,
version: "2.0.0".to_string(),
};
let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
assert!(err.to_string().contains("Unauthorized"));
}
// ========== 测试创建子合约 ==========
#[test]
fn test_create_child() {
let (mut deps, env) = setup_factory();
let info = mock_info("msg1user00000000000000000000000000000000000", &[]);
let init_msg = to_binary(&"init").unwrap();
let msg = FactoryExecuteMsg::CreateChild {
label: "child_1".to_string(),
owner: "msg1owner000000000000000000000000000000000".to_string(),
init_msg,
};
let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
// 验证产生了子消息
assert_eq!(res.messages.len(), 1);
}
// ========== 测试 Reply 处理 ==========
#[test]
fn test_reply_handler() {
let (mut deps, env) = setup_factory();
// 模拟 Reply 消息
let reply_msg = Reply {
id: CREATE_CHILD_REPLY_ID,
result: SubMsgResult::Ok(cosmwasm_std::ContractResult::Ok(
cosmwasm_std::Response::new()
.add_attribute("contract_address", "msg1childcontract000000000000000000")
.add_attribute("data", to_binary(&"some_data").unwrap().to_base64()),
)),
};
let res = reply(deps.as_mut(), env, reply_msg).unwrap();
assert_eq!(res.attributes[0].value, "child_created");
}
// ========== 测试批量迁移子合约 ==========
#[test]
fn test_migrate_children() {
let (mut deps, env) = setup_factory();
// 先创建一些子合约记录
let child1 = Addr::unchecked("msg1child10000000000000000000000000000000000");
let child2 = Addr::unchecked("msg1child20000000000000000000000000000000000");
CHILD_CONTRACTS
.save(
deps.as_mut().storage,
&child1,
&ChildContract {
address: child1.clone(),
code_id: 1,
version: "1.0.0".to_string(),
created_at: 100,
owner: Addr::unchecked("msg1owner000000000000000000000000000000000"),
},
)
.unwrap();
CHILD_CONTRACTS
.save(
deps.as_mut().storage,
&child2,
&ChildContract {
address: child2.clone(),
code_id: 1,
version: "1.0.0".to_string(),
created_at: 200,
owner: Addr::unchecked("msg1owner000000000000000000000000000000000"),
},
)
.unwrap();
// 升级 Code ID
FACTORY_CONFIG
.update(deps.as_mut().storage, |mut c| -> StdResult<_> {
c.current_code_id = 2;
c.version = "2.0.0".to_string();
Ok(c)
})
.unwrap();
// 批量迁移
let info = mock_info(FACTORY_ADMIN, &[]);
let msg = FactoryExecuteMsg::MigrateChildren {
child_addresses: vec![
"msg1child10000000000000000000000000000000000".to_string(),
"msg1child20000000000000000000000000000000000".to_string(),
],
migrate_msg: to_binary(&"migrate_to_v2").unwrap(),
};
let res = execute(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.attributes[0].value, "migrate_children");
assert_eq!(res.attributes[1].value, "2");
}
// ========== 测试工厂查询 ==========
#[test]
fn test_factory_queries() {
let (deps, env) = setup_factory();
// 查询配置
let msg = FactoryQueryMsg::GetConfig {};
let bin = query(deps.as_ref(), env, msg).unwrap();
let config: FactoryConfig = from_binary(&bin).unwrap();
assert_eq!(config.current_code_id, 1);
// 查询子合约列表(空)
let msg = FactoryQueryMsg::ListChildren {
start_after: None,
limit: None,
};
let bin = query(deps.as_ref(), mock_env(), msg).unwrap();
let children: Vec<ChildContract> = from_binary(&bin).unwrap();
assert!(children.is_empty());
}
}
4.3 工厂升级架构总结
工厂升级流程:
┌──────────────────────────────────────────┐
│ 1. 部署 V1 实现合约 → Code ID: 1 │
│ 2. 部署工厂合约,设置 initial_code_id: 1 │
│ 3. 用户通过工厂创建子合约 │
│ └─ 所有子合约使用 Code ID: 1 │
│ 4. 部署 V2 实现合约 → Code ID: 2 │
│ 5. 调用 factory.UpgradeCodeId(2, "2.0.0") │
│ └─ 新创建的子合约使用 Code ID: 2 │
│ 6. 分批迁移已有子合约到 Code ID: 2 │
│ └─ 兼容旧版本状态 │
└──────────────────────────────────────────┘
5. 状态迁移策略
5.1 Key 迁移与映射
合约升级中最常见的场景是状态结构的变化。以下是各种状态迁移模式:
use cosmwasm_std::{Addr, DepsMut, Env, Order, StdError, StdResult, Storage};
use cw_storage_plus::{Item, Map};
use serde::{Deserialize, Serialize};
// ========== V1 状态结构 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V1Config {
pub owner: Addr,
pub counter: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V1User {
pub name: String,
pub balance: u64,
}
// V1 的存储 key
pub const V1_CONFIG: Item<V1Config> = Item::new("config");
pub const V1_USERS: Map<&Addr, V1User> = Map::new("users");
// ========== V2 状态结构 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2Config {
pub owner: Addr,
pub counter: u64,
pub paused: bool, // 新增字段
pub last_updated: u64, // 新增字段
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2User {
pub name: String,
pub balance: u64,
pub created_at: u64, // 新增字段
pub is_active: bool, // 新增字段
}
// V2 的存储 key(使用不同前缀避免冲突)
pub const V2_CONFIG: Item<V2Config> = Item::new("v2_config");
pub const V2_USERS: Map<&Addr, V2User> = Map::new("v2_users");
// ========== 迁移映射表 ==========
/// 记录已迁移的 key,用于断点续传
pub const MIGRATION_MAP: Map<&Addr, bool> = Map::new("migrated");
/// 全局迁移状态
pub const MIGRATION_STATE: Item<MigrationState> = Item::new("migration_state");
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct MigrationState {
pub total_users: u64,
pub migrated_users: u64,
pub is_complete: bool,
pub started_at: u64,
pub completed_at: Option<u64>,
}
5.2 数据格式迁移
// ========== V1 → V2 迁移函数 ==========
/// 主迁移入口
pub fn migrate_state_v1_to_v2(
deps: &mut DepsMut,
env: &Env,
) -> StdResult<Response> {
// 1. 迁移配置
migrate_config_v1_to_v2(deps.storage, env)?;
// 2. 初始化迁移状态
let total = count_v1_users(deps.storage)?;
let mig_state = MigrationState {
total_users: total,
migrated_users: 0,
is_complete: false,
started_at: env.block.time.seconds(),
completed_at: None,
};
MIGRATION_STATE.save(deps.storage, &mig_state)?;
// 3. 迁移部分用户(分批处理)
let batch_size = 100u64;
migrate_users_batch(deps.storage, env, batch_size)?;
Ok(Response::new()
.add_attribute("method", "migrate_v1_to_v2")
.add_attribute("total_users", total.to_string()))
}
/// 配置迁移
fn migrate_config_v1_to_v2(
storage: &mut dyn Storage,
env: &Env,
) -> StdResult<()> {
// 读取 V1 配置
let v1_config = V1_CONFIG.load(storage)?;
// 转换为 V2 配置(设置默认值)
let v2_config = V2Config {
owner: v1_config.owner,
counter: v1_config.counter,
paused: false, // 新字段默认值
last_updated: env.block.time.seconds(), // 新字段
};
// 写入 V2 配置
V2_CONFIG.save(storage, &v2_config)?;
// 可选择清理旧 key
// V1_CONFIG.remove(storage);
Ok(())
}
/// 统计 V1 用户数
fn count_v1_users(storage: &dyn Storage) -> StdResult<u64> {
let count = V1_USERS
.keys(storage, None, None, Order::Ascending)
.count() as u64;
Ok(count)
}
/// 分批迁移用户
fn migrate_users_batch(
storage: &mut dyn Storage,
env: &Env,
batch_size: u64,
) -> StdResult<u64> {
let mut migrated = 0u64;
// 获取未迁移的用户
let users: Vec<Addr> = V1_USERS
.keys(storage, None, None, Order::Ascending)
.filter(|key| {
if let Ok(addr) = key {
// 跳过已迁移的
!MIGRATION_MAP.has(storage, addr)
} else {
false
}
})
.take(batch_size as usize)
.filter_map(|k| k.ok())
.collect();
for user_addr in &users {
// 读取 V1 用户数据
if let Ok(v1_user) = V1_USERS.load(storage, user_addr) {
// 转换为 V2 格式
let v2_user = V2User {
name: v1_user.name,
balance: v1_user.balance,
created_at: env.block.time.seconds(),
is_active: v1_user.balance > 0,
};
// 写入 V2 存储
V2_USERS.save(storage, user_addr, &v2_user)?;
// 标记已迁移
MIGRATION_MAP.save(storage, user_addr, &true)?;
migrated += 1;
}
}
// 更新迁移状态
MIGRATION_STATE.update(storage, |mut state| -> StdResult<_> {
state.migrated_users += migrated;
if state.migrated_users >= state.total_users {
state.is_complete = true;
state.completed_at = Some(env.block.time.seconds());
}
Ok(state)
})?;
Ok(migrated)
}
5.3 复杂状态迁移示例
// ========== 重构状态结构 ==========
/// V1: 扁平结构
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V1Account {
pub address: Addr,
pub token_a_balance: u64,
pub token_b_balance: u64,
pub staked_amount: u64,
pub rewards: u64,
pub last_claim: u64,
pub referral: Option<Addr>,
}
/// V2: 解耦结构
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2Account {
pub address: Addr,
pub created_at: u64,
pub is_blacklisted: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2TokenBalance {
pub address: Addr,
pub denom: String,
pub balance: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2StakingInfo {
pub address: Addr,
pub staked_amount: u64,
pub rewards: u64,
pub last_claim: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct V2ReferralInfo {
pub address: Addr,
pub referrer: Addr,
pub bonus_earned: u64,
}
// V2 存储
pub const V2_ACCOUNTS: Map<&Addr, V2Account> = Map::new("v2_accounts");
pub const V2_TOKEN_BALANCES: Map<(&Addr, &str), V2TokenBalance> =
Map::new("v2_token_balances");
pub const V2_STAKING: Map<&Addr, V2StakingInfo> = Map::new("v2_staking");
pub const V2_REFERRALS: Map<&Addr, V2ReferralInfo> = Map::new("v2_referrals");
// ========== 复杂迁移逻辑 ==========
pub fn complex_migration_v1_to_v2(
storage: &mut dyn Storage,
env: &Env,
) -> StdResult<u64> {
let mut migrated = 0u64;
// 遍历所有 V1 账户
let accounts: Vec<Addr> = V1_ACCOUNTS
.keys(storage, None, None, Order::Ascending)
.filter_map(|k| k.ok())
.collect();
for addr in &accounts {
if let Ok(v1) = V1_ACCOUNTS.load(storage, addr) {
// 1. 创建 V2 账户
let v2_account = V2Account {
address: addr.clone(),
created_at: env.block.time.seconds(),
is_blacklisted: false,
};
V2_ACCOUNTS.save(storage, addr, &v2_account)?;
// 2. 拆分 Token 余额
let token_a = V2TokenBalance {
address: addr.clone(),
denom: "utoken_a".to_string(),
balance: v1.token_a_balance,
};
V2_TOKEN_BALANCES.save(
storage,
&(addr.clone(), "utoken_a"),
&token_a,
)?;
let token_b = V2TokenBalance {
address: addr.clone(),
denom: "utoken_b".to_string(),
balance: v1.token_b_balance,
};
V2_TOKEN_BALANCES.save(
storage,
&(addr.clone(), "utoken_b"),
&token_b,
)?;
// 3. 创建 Staking 信息
let staking = V2StakingInfo {
address: addr.clone(),
staked_amount: v1.staked_amount,
rewards: v1.rewards,
last_claim: v1.last_claim,
};
V2_STAKING.save(storage, addr, &staking)?;
// 4. 处理推荐关系
if let Some(referrer) = v1.referral {
let referral = V2ReferralInfo {
address: addr.clone(),
referrer,
bonus_earned: 0,
};
V2_REFERRALS.save(storage, addr, &referral)?;
}
migrated += 1;
}
}
Ok(migrated)
}
const V1_ACCOUNTS: Map<&Addr, V1Account> = Map::new("v1_accounts");
5.4 向后兼容策略
// ========== 双读策略 ==========
/// 在迁移完成前,同时兼容 V1 和 V2 的读取
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct UserData {
pub name: String,
pub balance: u64,
pub created_at: u64,
pub is_active: bool,
}
/// 兼容读取函数:优先读 V2,回退到 V1
pub fn read_user_compat(
storage: &dyn Storage,
addr: &Addr,
) -> StdResult<UserData> {
// 尝试读取 V2
if let Ok(v2) = V2_USERS.load(storage, addr) {
return Ok(UserData {
name: v2.name,
balance: v2.balance,
created_at: v2.created_at,
is_active: v2.is_active,
});
}
// 回退到 V1
let v1 = V1_USERS.load(storage, addr)?;
Ok(UserData {
name: v1.name,
balance: v1.balance,
created_at: 0, // V1 没有此字段
is_active: true, // V1 默认活跃
})
}
// ========== 惰性迁移 ==========
/// 在读取时自动迁移(适用于低并发场景)
pub fn read_and_migrate_user(
storage: &mut dyn Storage,
env: &Env,
addr: &Addr,
) -> StdResult<UserData> {
// 如果已经迁移,直接读 V2
if MIGRATION_MAP.has(storage, addr) {
let v2 = V2_USERS.load(storage, addr)?;
return Ok(UserData {
name: v2.name,
balance: v2.balance,
created_at: v2.created_at,
is_active: v2.is_active,
});
}
// 读取 V1 并迁移
let v1 = V1_USERS.load(storage, addr)?;
let v2 = V2User {
name: v1.name.clone(),
balance: v1.balance,
created_at: env.block.time.seconds(),
is_active: v1.balance > 0,
};
// 写入 V2
V2_USERS.save(storage, addr, &v2)?;
MIGRATION_MAP.save(storage, addr, &true)?;
Ok(UserData {
name: v1.name,
balance: v1.balance,
created_at: env.block.time.seconds(),
is_active: v1.balance > 0,
})
}
// ========== 迁移状态查询 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct MigrationStatus {
pub is_complete: bool,
pub total_users: u64,
pub migrated_users: u64,
pub progress_pct: f64,
pub started_at: u64,
pub completed_at: Option<u64>,
}
pub fn get_migration_status(
storage: &dyn Storage,
) -> StdResult<MigrationStatus> {
let state = MIGRATION_STATE.load(storage)?;
Ok(MigrationStatus {
is_complete: state.is_complete,
total_users: state.total_users,
migrated_users: state.migrated_users,
progress_pct: if state.total_users > 0 {
(state.migrated_users as f64 / state.total_users as f64) * 100.0
} else {
100.0
},
started_at: state.started_at,
completed_at: state.completed_at,
})
}
5.5 状态迁移测试
#[cfg(test)]
mod migration_tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env};
#[test]
fn test_config_migration() {
let mut deps = mock_dependencies();
let env = mock_env();
// 写入 V1 配置
let v1_config = V1Config {
owner: Addr::unchecked("msg1owner000000000000000000000000000000000"),
counter: 42,
};
V1_CONFIG.save(deps.as_mut().storage, &v1_config).unwrap();
// 执行迁移
migrate_config_v1_to_v2(deps.as_mut().storage, &env).unwrap();
// 验证 V2 配置
let v2_config = V2_CONFIG.load(&deps.storage).unwrap();
assert_eq!(v2_config.owner, v1_config.owner);
assert_eq!(v2_config.counter, 42);
assert!(!v2_config.paused);
assert_eq!(
v2_config.last_updated,
env.block.time.seconds()
);
}
#[test]
fn test_user_migration_batch() {
let mut deps = mock_dependencies();
let env = mock_env();
// 创建 V1 用户
for i in 0..50u64 {
let addr = Addr::unchecked(format!("msg1user{:032x}", i));
let user = V1User {
name: format!("user_{}", i),
balance: i * 100,
};
V1_USERS
.save(deps.as_mut().storage, &addr, &user)
.unwrap();
}
// 迁移配置
migrate_config_v1_to_v2(deps.as_mut().storage, &env).unwrap();
// 迁移第一批(30 个)
let migrated = migrate_users_batch(
deps.as_mut().storage,
&env,
30,
)
.unwrap();
assert_eq!(migrated, 30);
// 检查迁移状态
let state = MIGRATION_STATE.load(&deps.storage).unwrap();
assert!(!state.is_complete);
assert_eq!(state.migrated_users, 30);
assert_eq!(state.total_users, 50);
// 迁移剩余
let remaining = migrate_users_batch(
deps.as_mut().storage,
&env,
30,
)
.unwrap();
assert_eq!(remaining, 20);
// 验证完成
let state = MIGRATION_STATE.load(&deps.storage).unwrap();
assert!(state.is_complete);
assert_eq!(state.migrated_users, 50);
}
#[test]
fn test_compat_read() {
let mut deps = mock_dependencies();
let env = mock_env();
let addr = Addr::unchecked("msg1user00000000000000000000000000000000000");
// 只有 V1 数据
let v1 = V1User {
name: "alice".to_string(),
balance: 1000,
};
V1_USERS.save(deps.as_mut().storage, &addr, &v1).unwrap();
// 兼容读取应成功
let data = read_user_compat(&deps.storage, &addr).unwrap();
assert_eq!(data.name, "alice");
assert_eq!(data.balance, 1000);
assert_eq!(data.created_at, 0); // V1 默认值
assert!(data.is_active);
// 迁移后
migrate_config_v1_to_v2(deps.as_mut().storage, &env).unwrap();
migrate_users_batch(deps.as_mut().storage, &env, 100).unwrap();
// 兼容读取应读取 V2 数据
let data = read_user_compat(&deps.storage, &addr).unwrap();
assert_eq!(data.name, "alice");
assert_eq!(data.balance, 1000);
assert_eq!(data.created_at, env.block.time.seconds());
assert!(data.is_active);
}
#[test]
fn test_migration_status_query() {
let mut deps = mock_dependencies();
let env = mock_env();
// 初始化迁移状态
let state = MigrationState {
total_users: 1000,
migrated_users: 750,
is_complete: false,
started_at: env.block.time.seconds(),
completed_at: None,
};
MIGRATION_STATE
.save(deps.as_mut().storage, &state)
.unwrap();
let status = get_migration_status(&deps.storage).unwrap();
assert_eq!(status.total_users, 1000);
assert_eq!(status.migrated_users, 750);
assert!((status.progress_pct - 75.0).abs() < f64::EPSILON);
}
}
6. AI Agent 合约升级模式
6.1 AI Agent 系统架构
在 MSG Chain 上构建 AI Agent 系统时,合约升级需要考虑多合约协调:
MSG Chain AI Agent 系统
├── Agent Registry → 管理 Agent 元数据
├── A2A Protocol → Agent 间通信协议
├── Payment Contract → 支付与结算
└── Constitution → DAO 治理规则
6.2 Agent Registry 升级协调
use cosmwasm_std::{
entry_point, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env,
MessageInfo, Order, Reply, Response, StdError, StdResult, Storage,
SubMsg, WasmMsg,
};
use cw_storage_plus::{Item, Map};
use serde::{Deserialize, Serialize};
// ========== Agent 注册表状态 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct AgentInfo {
pub address: Addr,
pub name: String,
pub description: String,
pub creator: Addr,
pub version: String,
pub protocol_version: String,
pub status: AgentStatus,
pub created_at: u64,
pub metadata_uri: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum AgentStatus {
Active,
Paused,
Deprecated,
Banned,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct RegistryConfig {
pub admin: Addr,
pub min_protocol_version: String,
pub max_agents_per_creator: u32,
pub paused: bool,
}
pub const REGISTRY_CONFIG: Item<RegistryConfig> = Item::new("registry_config");
pub const AGENTS: Map<&Addr, AgentInfo> = Map::new("agents");
pub const AGENT_BY_NAME: Map<&str, Addr> = Map::new("agent_by_name");
// ========== Registry 消息 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct RegistryInstantiateMsg {
pub admin: String,
pub min_protocol_version: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RegistryExecuteMsg {
RegisterAgent {
name: String,
description: String,
protocol_version: String,
metadata_uri: Option<String>,
},
UpdateAgent {
name: Option<String>,
description: Option<String>,
protocol_version: Option<String>,
metadata_uri: Option<String>,
},
SetAgentStatus {
agent: String,
status: AgentStatus,
},
UpgradeProtocol {
new_min_version: String,
},
Pause {},
Unpause {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RegistryQueryMsg {
GetAgent { address: String },
GetAgentByName { name: String },
ListAgents {
start_after: Option<String>,
limit: Option<u32>,
status: Option<AgentStatus>,
},
GetConfig {},
GetProtocolVersion {},
}
// ========== Registry 迁移 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct RegistryMigrateMsg {
pub version: String,
pub upgrade_agents: bool,
pub new_protocol_version: Option<String>,
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
deps: DepsMut,
env: Env,
msg: RegistryMigrateMsg,
) -> StdResult<Response> {
let old_version = deps.contract_info.version.clone();
// 版本检查
if old_version == msg.version {
return Err(StdError::generic_err("Already at target version"));
}
// 升级协议版本(如需)
if let Some(new_proto) = &msg.new_protocol_version {
REGISTRY_CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.min_protocol_version = new_proto.clone();
Ok(config)
})?;
}
// 批量升级 Agent 协议版本
if msg.upgrade_agents {
let mut count = 0u64;
let agents: Vec<Addr> = AGENTS
.keys(deps.storage, None, None, Order::Ascending)
.filter_map(|k| k.ok())
.collect();
for addr in &agents {
AGENTS.update(deps.storage, addr, |agent| -> StdResult<_> {
let mut a = agent.ok_or_else(|| {
StdError::generic_err(format!("Agent not found: {}", addr))
})?;
if let Some(ref new_proto) = msg.new_protocol_version {
a.protocol_version = new_proto.clone();
}
a.version = msg.version.clone();
Ok(a)
})?;
count += 1;
}
return Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("from_version", &old_version)
.add_attribute("to_version", &msg.version)
.add_attribute("agents_upgraded", count.to_string())
.add_attribute("new_protocol_version", msg.new_protocol_version.unwrap_or_default()));
}
Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("from_version", &old_version)
.add_attribute("to_version", &msg.version))
}
// ========== Registry 执行实现 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: RegistryExecuteMsg,
) -> StdResult<Response> {
match msg {
RegistryExecuteMsg::RegisterAgent {
name,
description,
protocol_version,
metadata_uri,
} => execute_register_agent(
deps, env, info, name, description, protocol_version, metadata_uri,
),
RegistryExecuteMsg::UpdateAgent {
name,
description,
protocol_version,
metadata_uri,
} => execute_update_agent(
deps, env, info, name, description, protocol_version, metadata_uri,
),
RegistryExecuteMsg::SetAgentStatus {
agent,
status,
} => execute_set_agent_status(deps, env, info, agent, status),
RegistryExecuteMsg::UpgradeProtocol {
new_min_version,
} => execute_upgrade_protocol(deps, env, info, new_min_version),
RegistryExecuteMsg::Pause {} => execute_pause(deps, env, info),
RegistryExecuteMsg::Unpause {} => execute_unpause(deps, env, info),
}
}
fn execute_register_agent(
deps: DepsMut,
env: Env,
info: MessageInfo,
name: String,
description: String,
protocol_version: String,
metadata_uri: Option<String>,
) -> StdResult<Response> {
let config = REGISTRY_CONFIG.load(deps.storage)?;
// 检查名称唯一
if AGENT_BY_NAME.has(deps.storage, &name) {
return Err(StdError::generic_err("Agent name already exists"));
}
// 检查协议版本兼容性
if protocol_version.as_str() < config.min_protocol_version.as_str() {
return Err(StdError::generic_err(format!(
"Protocol version {} is below minimum {}",
protocol_version, config.min_protocol_version
)));
}
let agent = AgentInfo {
address: info.sender.clone(),
name: name.clone(),
description,
creator: info.sender.clone(),
version: "1.0.0".to_string(),
protocol_version,
status: AgentStatus::Active,
created_at: env.block.time.seconds(),
metadata_uri,
};
AGENTS.save(deps.storage, &info.sender, &agent)?;
AGENT_BY_NAME.save(deps.storage, &name, &info.sender)?;
Ok(Response::new()
.add_attribute("method", "register_agent")
.add_attribute("agent", info.sender)
.add_attribute("name", name))
}
fn execute_update_agent(
deps: DepsMut,
_env: Env,
info: MessageInfo,
name: Option<String>,
description: Option<String>,
protocol_version: Option<String>,
metadata_uri: Option<String>,
) -> StdResult<Response> {
AGENTS.update(deps.storage, &info.sender, |agent| -> StdResult<_> {
let mut a = agent.ok_or_else(|| {
StdError::generic_err("Agent not found")
})?;
if let Some(n) = name {
// 更新名称映射
AGENT_BY_NAME.remove(deps.storage, &a.name);
AGENT_BY_NAME.save(deps.storage, &n, &info.sender)?;
a.name = n;
}
if let Some(d) = description {
a.description = d;
}
if let Some(p) = protocol_version {
a.protocol_version = p;
}
if let Some(m) = metadata_uri {
a.metadata_uri = Some(m);
}
Ok(a)
})?;
Ok(Response::new().add_attribute("method", "update_agent"))
}
fn execute_set_agent_status(
deps: DepsMut,
_env: Env,
info: MessageInfo,
agent: String,
status: AgentStatus,
) -> StdResult<Response> {
let config = REGISTRY_CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
let agent_addr = deps.api.addr_validate(&agent)?;
AGENTS.update(deps.storage, &agent_addr, |a| -> StdResult<_> {
let mut agent = a.ok_or_else(|| StdError::generic_err("Agent not found"))?;
agent.status = status;
Ok(agent)
})?;
Ok(Response::new().add_attribute("method", "set_agent_status"))
}
fn execute_upgrade_protocol(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_min_version: String,
) -> StdResult<Response> {
let config = REGISTRY_CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
REGISTRY_CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.min_protocol_version = new_min_version.clone();
Ok(config)
})?;
Ok(Response::new()
.add_attribute("method", "upgrade_protocol")
.add_attribute("new_min_version", new_min_version))
}
6.3 A2A 协议版本协商
// ========== A2A 协议版本管理 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ProtocolVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl ProtocolVersion {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self { major, minor, patch }
}
pub fn to_string(&self) -> String {
format!("{}.{}.{}", self.major, self.minor, self.patch)
}
/// 检查是否兼容(同 major 版本兼容)
pub fn is_compatible_with(&self, other: &ProtocolVersion) -> bool {
self.major == other.major
}
/// 检查是否高于对方
pub fn is_higher_than(&self, other: &ProtocolVersion) -> bool {
(self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct A2AHandshake {
pub agent_address: Addr,
pub supported_versions: Vec<ProtocolVersion>,
pub preferred_version: ProtocolVersion,
pub capabilities: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct A2ANegotiationResult {
pub negotiated_version: ProtocolVersion,
pub agent_a: Addr,
pub agent_b: Addr,
pub shared_capabilities: Vec<String>,
}
/// A2A 协议版本协商逻辑
pub fn negotiate_protocol(
handshake_a: &A2AHandshake,
handshake_b: &A2AHandshake,
) -> StdResult<A2ANegotiationResult> {
// 找到双方都支持的最高版本
let mut negotiated: Option<ProtocolVersion> = None;
for version in &handshake_a.supported_versions {
if handshake_b.supported_versions.contains(version) {
match &negotiated {
Some(current) => {
if version.is_higher_than(current) {
negotiated = Some(version.clone());
}
}
None => {
negotiated = Some(version.clone());
}
}
}
}
let negotiated_version = negotiated.ok_or_else(|| {
StdError::generic_err("No compatible protocol version found between agents")
})?;
// 计算共享能力
let shared_capabilities: Vec<String> = handshake_a
.capabilities
.iter()
.filter(|c| handshake_b.capabilities.contains(c))
.cloned()
.collect();
Ok(A2ANegotiationResult {
negotiated_version,
agent_a: handshake_a.agent_address.clone(),
agent_b: handshake_b.agent_address.clone(),
shared_capabilities,
})
}
// ========== 协议升级迁移 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ProtocolUpgradeMsg {
pub new_version: ProtocolVersion,
pub force_upgrade: bool,
pub grace_period_blocks: Option<u64>,
}
/// 协议升级时的兼容性检查
pub fn validate_protocol_upgrade(
current: &ProtocolVersion,
target: &ProtocolVersion,
) -> StdResult<()> {
// major 版本跳级需要谨慎
if target.major > current.major + 1 {
return Err(StdError::generic_err(format!(
"Cannot skip major version: {} -> {}",
current.major, target.major
)));
}
// 不允许降级
if target.major < current.major
|| (target.major == current.major && target.minor < current.minor)
{
return Err(StdError::generic_err("Protocol downgrade is not allowed"));
}
Ok(())
}
6.4 支付合约迁移(不断开会话)
// ========== 支付合约状态 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaymentSession {
pub session_id: String,
pub agent_a: Addr,
pub agent_b: Addr,
pub total_amount: u64,
pub paid_amount: u64,
pub status: SessionStatus,
pub created_at: u64,
pub last_activity: u64,
pub metadata: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum SessionStatus {
Active,
Completed,
Disputed,
Cancelled,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaymentConfig {
pub admin: Addr,
pub fee_rate: u64,
pub min_settlement: u64,
pub paused: bool,
}
pub const PAYMENT_CONFIG: Item<PaymentConfig> = Item::new("payment_config");
pub const SESSIONS: Map<&str, PaymentSession> = Map::new("sessions");
// ========== 支付合约迁移 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaymentMigrateMsg {
pub version: String,
pub new_fee_rate: Option<u64>,
pub preserve_sessions: bool,
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
deps: DepsMut,
_env: Env,
msg: PaymentMigrateMsg,
) -> StdResult<Response> {
// 版本检查
if deps.contract_info.version == msg.version {
return Err(StdError::generic_err("Already at target version"));
}
// 更新费率
if let Some(fee) = msg.new_fee_rate {
PAYMENT_CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
config.fee_rate = fee;
Ok(config)
})?;
}
let mut response = Response::new()
.add_attribute("method", "migrate")
.add_attribute("from", &deps.contract_info.version)
.add_attribute("to", &msg.version);
// 保留所有活跃会话
if msg.preserve_sessions {
let active_count = count_active_sessions(deps.storage)?;
response = response
.add_attribute("preserved_sessions", active_count.to_string());
}
Ok(response)
}
fn count_active_sessions(storage: &dyn Storage) -> StdResult<u64> {
let count = SESSIONS
.range(storage, None, None, Order::Ascending)
.filter(|item| {
if let Ok((_, session)) = item {
session.status == SessionStatus::Active
} else {
false
}
})
.count() as u64;
Ok(count)
}
// ========== 支付执行 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: PaymentExecuteMsg,
) -> StdResult<Response> {
match msg {
PaymentExecuteMsg::CreateSession {
agent_b,
total_amount,
metadata,
} => execute_create_session(deps, env, info, agent_b, total_amount, metadata),
PaymentExecuteMsg::Pay {
session_id,
amount,
} => execute_pay(deps, env, info, session_id, amount),
PaymentExecuteMsg::Settle {
session_id,
} => execute_settle(deps, env, info, session_id),
PaymentExecuteMsg::CancelSession {
session_id,
} => execute_cancel_session(deps, env, info, session_id),
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PaymentExecuteMsg {
CreateSession {
agent_b: String,
total_amount: u64,
metadata: Option<String>,
},
Pay {
session_id: String,
amount: u64,
},
Settle {
session_id: String,
},
CancelSession {
session_id: String,
},
}
fn execute_create_session(
deps: DepsMut,
env: Env,
info: MessageInfo,
agent_b: String,
total_amount: u64,
metadata: Option<String>,
) -> StdResult<Response> {
let config = PAYMENT_CONFIG.load(deps.storage)?;
let agent_b_addr = deps.api.addr_validate(&agent_b)?;
let session_id = format!(
"{}-{}-{}",
info.sender.as_str(),
agent_b_addr.as_str(),
env.block.time.seconds()
);
let session = PaymentSession {
session_id: session_id.clone(),
agent_a: info.sender,
agent_b: agent_b_addr,
total_amount,
paid_amount: 0,
status: SessionStatus::Active,
created_at: env.block.time.seconds(),
last_activity: env.block.time.seconds(),
metadata,
};
SESSIONS.save(deps.storage, &session_id, &session)?;
Ok(Response::new()
.add_attribute("method", "create_session")
.add_attribute("session_id", session_id))
}
fn execute_pay(
deps: DepsMut,
env: Env,
_info: MessageInfo,
session_id: String,
amount: u64,
) -> StdResult<Response> {
SESSIONS.update(deps.storage, &session_id, |session| -> StdResult<_> {
let mut s = session.ok_or_else(|| {
StdError::generic_err("Session not found")
})?;
s.paid_amount += amount;
s.last_activity = env.block.time.seconds();
Ok(s)
})?;
Ok(Response::new()
.add_attribute("method", "pay")
.add_attribute("session_id", session_id)
.add_attribute("amount", amount.to_string()))
}
fn execute_settle(
deps: DepsMut,
env: Env,
info: MessageInfo,
session_id: String,
) -> StdResult<Response> {
let config = PAYMENT_CONFIG.load(deps.storage)?;
SESSIONS.update(deps.storage, &session_id, |session| -> StdResult<_> {
let mut s = session.ok_or_else(|| {
StdError::generic_err("Session not found")
})?;
if info.sender != s.agent_a && info.sender != s.agent_b && info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized: not party to session"));
}
if s.paid_amount < s.total_amount {
return Err(StdError::generic_err(format!(
"Session underfunded: paid {} / total {}",
s.paid_amount, s.total_amount
)));
}
s.status = SessionStatus::Completed;
s.last_activity = env.block.time.seconds();
Ok(s)
})?;
Ok(Response::new()
.add_attribute("method", "settle")
.add_attribute("session_id", session_id))
}
fn execute_cancel_session(
deps: DepsMut,
env: Env,
info: MessageInfo,
session_id: String,
) -> StdResult<Response> {
SESSIONS.update(deps.storage, &session_id, |session| -> StdResult<_> {
let mut s = session.ok_or_else(|| {
StdError::generic_err("Session not found")
})?;
if info.sender != s.agent_a && info.sender != s.agent_b {
return Err(StdError::generic_err("Unauthorized: not party to session"));
}
s.status = SessionStatus::Cancelled;
s.last_activity = env.block.time.seconds();
Ok(s)
})?;
Ok(Response::new()
.add_attribute("method", "cancel_session")
.add_attribute("session_id", session_id))
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PaymentQueryMsg {
GetSession { session_id: String },
ListSessionsByAgent { agent: String, status: Option<SessionStatus> },
GetConfig {},
}
6.5 DAO 治理升级(Constitution)
// ========== DAO Constitution 合约 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Constitution {
pub version: String,
pub articles: Vec<Article>,
pub voting_threshold: u64, // 百分比 (e.g., 66 = 66%)
pub min_voting_period: u64, // 区块数
pub upgraded_at: u64,
pub proposed_by: Option<Addr>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Article {
pub id: u32,
pub title: String,
pub content: String,
pub is_active: bool,
}
pub const CONSTITUTION: Item<Constitution> = Item::new("constitution");
pub const VOTES: Map<&Addr, VoteRecord> = Map::new("votes");
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct VoteRecord {
pub voter: Addr,
pub proposal_id: String,
pub vote: Vote,
pub voted_at: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Vote {
Yes,
No,
Abstain,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct UpgradeProposal {
pub id: String,
pub proposer: Addr,
pub new_constitution: Constitution,
pub created_at: u64,
pub voting_end: u64,
pub yes_votes: u64,
pub no_votes: u64,
pub abstain_votes: u64,
pub total_voting_power: u64,
pub executed: bool,
}
pub const PROPOSALS: Map<&str, UpgradeProposal> = Map::new("proposals");
// ========== DAO 治理执行 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ConstitutionExecuteMsg {
ProposeUpgrade {
new_constitution: Constitution,
},
Vote {
proposal_id: String,
vote: Vote,
},
ExecuteUpgrade {
proposal_id: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ConstitutionMigrateMsg {
pub version: String,
pub emergency_override: bool,
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
deps: DepsMut,
env: Env,
msg: ConstitutionMigrateMsg,
) -> StdResult<Response> {
if deps.contract_info.version == msg.version {
return Err(StdError::generic_err("Already at target version"));
}
let current = CONSTITUTION.load(deps.storage)?;
// 紧急升级覆盖(需要 DAO 多签或 TimeLock)
if msg.emergency_override {
// 记录紧急升级事件
return Ok(Response::new()
.add_attribute("method", "emergency_migrate")
.add_attribute("from", &deps.contract_info.version)
.add_attribute("to", &msg.version)
.add_attribute("voting_threshold", current.voting_threshold.to_string()));
}
// 正常升级流程(遵循 Constitution 的治理规则)
Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("from", &deps.contract_info.version)
.add_attribute("to", &msg.version)
.add_attribute("note", "Constitution upgrade requires proposal and vote"))
}
7. 安全考虑
7.1 治理控制
// ========== 多签治理控制 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct GovernanceConfig {
pub multisig_address: Option<Addr>,
pub dao_address: Option<Addr>,
pub upgrade_threshold: u64, // 需要签名数
pub total_signers: u64,
pub timelock_blocks: u64, // 升级延迟区块数
pub emergency_admin: Option<Addr>, // 紧急情况单人管理员
}
pub const GOVERNANCE: Item<GovernanceConfig> = Item::new("governance");
/// 验证升级权限
pub fn validate_upgrade_authority(
storage: &dyn Storage,
sender: &Addr,
) -> StdResult<()> {
let gov = GOVERNANCE.load(storage)?;
// 检查是否为紧急管理员
if let Some(ref admin) = gov.emergency_admin {
if sender == admin {
return Ok(()); // 紧急管理员可绕过检查
}
}
// 检查是否为 DAO 合约
if let Some(ref dao) = gov.dao_address {
if sender == dao {
return Ok(()); // DAO 投票通过后可执行
}
}
// 检查是否为多签合约
if let Some(ref ms) = gov.multisig_address {
if sender == ms {
return Ok(()); // 多签已确认
}
}
Err(StdError::generic_err("Unauthorized upgrade source"))
}
7.2 TimeLock 延迟升级
// ========== Timelock 机制 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct TimelockEntry {
pub target_contract: Addr,
pub new_code_id: u64,
pub migrate_msg: Binary,
pub proposed_at: u64,
pub executes_at: u64,
pub proposer: Addr,
pub executed: bool,
}
pub const TIMELOCK_QUEUE: Map<u64, TimelockEntry> = Map::new("timelock");
pub const TIMELOCK_NONCE: Item<u64> = Item::new("timelock_nonce");
/// 提交升级提案(进入 Timelock)
pub fn propose_upgrade(
deps: DepsMut,
env: Env,
proposer: Addr,
target_contract: Addr,
new_code_id: u64,
migrate_msg: Binary,
timelock_blocks: u64,
) -> StdResult<Response> {
validate_upgrade_authority(deps.storage, &proposer)?;
let mut nonce = TIMELOCK_NONCE
.load(deps.storage)
.unwrap_or(0);
nonce += 1;
let entry = TimelockEntry {
target_contract,
new_code_id,
migrate_msg,
proposed_at: env.block.time.seconds(),
executes_at: env.block.height + timelock_blocks,
proposer,
executed: false,
};
TIMELOCK_QUEUE.save(deps.storage, &nonce, &entry)?;
TIMELOCK_NONCE.save(deps.storage, &nonce)?;
Ok(Response::new()
.add_attribute("method", "propose_upgrade")
.add_attribute("nonce", nonce.to_string())
.add_attribute("executes_at", entry.executes_at.to_string()))
}
/// 执行 Timelock 中的升级
pub fn execute_timelocked_upgrade(
deps: DepsMut,
env: Env,
executor: Addr,
nonce: u64,
) -> StdResult<Response> {
let mut entry = TIMELOCK_QUEUE.load(deps.storage, &nonce)?;
if entry.executed {
return Err(StdError::generic_err("Upgrade already executed"));
}
if env.block.height < entry.executes_at {
return Err(StdError::generic_err(format!(
"Timelock not expired: current {} < target {}",
env.block.height, entry.executes_at
)));
}
entry.executed = true;
TIMELOCK_QUEUE.save(deps.storage, &nonce, &entry)?;
// 构造迁移消息
let migrate_msg = WasmMsg::Migrate {
contract_addr: entry.target_contract.to_string(),
new_code_id: entry.new_code_id,
msg: entry.migrate_msg,
};
Ok(Response::new()
.add_message(migrate_msg)
.add_attribute("method", "execute_upgrade")
.add_attribute("nonce", nonce.to_string()))
}
7.3 紧急降级路径
// ========== 紧急回滚机制 ==========
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct EmergencyState {
pub previous_code_id: u64, // 上一个 Code ID
pub previous_version: String, // 上一个版本
pub rollback_count: u64, // 回滚次数
pub last_rollback: u64, // 最后回滚时间
pub cooldown_blocks: u64, // 回滚冷却期
}
pub const EMERGENCY_STATE: Item<EmergencyState> = Item::new("emergency_state");
/// 紧急回滚到上一个版本
pub fn emergency_rollback(
deps: DepsMut,
env: Env,
sender: Addr,
) -> StdResult<Response> {
// 检查权限(仅紧急管理员或 DAO)
let gov = GOVERNANCE.load(deps.storage)?;
let is_authorized = match (&gov.emergency_admin, &gov.dao_address) {
(Some(admin), _) if sender == *admin => true,
(_, Some(dao)) if sender == *dao => true,
_ => false,
};
if !is_authorized {
return Err(StdError::generic_err("Unauthorized emergency rollback"));
}
let emergency = EMERGENCY_STATE.load(deps.storage)?;
// 检查冷却期
if env.block.height < emergency.last_rollback + emergency.cooldown_blocks {
return Err(StdError::generic_err(format!(
"Rollback cooldown active: {} blocks remaining",
emergency.last_rollback + emergency.cooldown_blocks - env.block.height
)));
}
// 记录回滚
EMERGENCY_STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.rollback_count += 1;
state.last_rollback = env.block.height;
Ok(state)
})?;
// 构造回滚迁移消息
let rollback_msg = WasmMsg::Migrate {
contract_addr: env.contract.address.to_string(),
new_code_id: emergency.previous_code_id,
msg: to_binary(&MigrateMsg {
version: emergency.previous_version.clone(),
})?,
};
Ok(Response::new()
.add_message(rollback_msg)
.add_attribute("method", "emergency_rollback")
.add_attribute("rollback_to_code_id", emergency.previous_code_id.to_string())
.add_attribute("rollback_to_version", &emergency.previous_version))
}
// ========== 升级前快照 ==========
/// 在迁移入口点自动保存快照,用于回滚
pub fn save_rollback_snapshot(
deps: &mut DepsMut,
env: &Env,
) -> StdResult<()> {
let emergency = EmergencyState {
previous_code_id: deps.contract_info.code_id,
previous_version: deps.contract_info.version.clone(),
rollback_count: 0,
last_rollback: env.block.height,
cooldown_blocks: 100, // 100 个区块的冷却期
};
EMERGENCY_STATE.save(deps.storage, &emergency)?;
Ok(())
}
7.4 安全最佳实践总结
// ========== 升级安全检查清单 ==========
/// 1. 版本验证
/// - 确保目标版本高于当前版本
/// - 防止重放攻击(相同版本号禁止重复迁移)
///
/// 2. 权限控制
/// - 合约管理员(Admin)必须安全设置
/// - 推荐使用多签或 DAO 治理
/// - 紧急降级路径需要单独授权
///
/// 3. 状态完整性
/// - 迁移前备份状态(快照)
/// - 迁移过程中保持原子性
/// - 提供断点续传能力
///
/// 4. Timelock 延迟
/// - 所有升级提案至少延迟 24 小时
/// - 用户可在延迟期内退出或反对
///
/// 5. 测试要求
/// - 每次升级前必须有完整的测试覆盖
/// - 测试包括:正向迁移、反向兼容、失败回滚
///
/// 6. 监控与告警
/// - 升级事件必须记录完整链上日志
/// - 异常迁移行为自动触发暂停
///
/// MSG Chain 特定注意事项:
/// - 地址前缀: msg
/// - 链 ID: msg-chain-1
/// - 确保所有硬编码地址使用 msg 前缀
/// - 测试网与主网使用不同的 Code ID
// ========== 安全升级示例 ==========
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> {
// 1. 验证版本
let current_version = deps.contract_info.version.clone();
assert_valid_migration(¤t_version, &msg.version)?;
// 2. 保存回滚快照
save_rollback_snapshot(&mut deps, &env)?;
// 3. 执行状态迁移
execute_state_migration(deps.storage, &env, ¤t_version, &msg.version)?;
// 4. 记录审计日志
Ok(Response::new()
.add_attribute("method", "secure_migrate")
.add_attribute("from", current_version)
.add_attribute("to", msg.version)
.add_attribute("contract", env.contract.address)
.add_attribute("timestamp", env.block.time.seconds().to_string()))
}
fn execute_state_migration(
_storage: &mut dyn Storage,
_env: &Env,
_from: &str,
_to: &str,
) -> StdResult<()> {
// 实际迁移逻辑
Ok(())
}
7.5 完整迁移安全测试
#[cfg(test)]
mod security_tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
#[test]
fn test_unauthorized_migration_rejected() {
let mut deps = mock_dependencies();
let env = mock_env();
// 未设置管理员时
deps.contract_info.admin = None;
deps.contract_info.version = "1.0.0".to_string();
// migrate 本身不需要管理员检查(链层负责),
// 但合约内部应校验
let msg = MigrateMsg {
version: "2.0.0".to_string(),
};
// 无管理员时,migrate 仍可执行(链层验证在外部)
// 所以我们在 migrate 中增加内部检查
let result = migrate(deps.as_mut(), env, msg);
assert!(result.is_ok()); // 合约内部可能不检查管理员,但链层会检查
}
#[test]
fn test_version_downgrade_rejected() {
let mut deps = mock_dependencies();
let env = mock_env();
deps.contract_info.version = "2.0.0".to_string();
// 尝试降级
let msg = MigrateMsg {
version: "1.0.0".to_string(),
};
// 如果版本不同,migrate 会执行
// 生产环境中应使用 semver 检查
let result = migrate(deps.as_mut(), env, msg);
assert!(result.is_ok()); // 基础版本不阻止降级,需自定义
}
#[test]
fn test_timelock_enforces_delay() {
let mut deps = mock_dependencies();
let mut env = mock_env();
// 设置治理配置
let gov = GovernanceConfig {
multisig_address: None,
dao_address: Some(Addr::unchecked("msg1dao0000000000000000000000000000000000000")),
upgrade_threshold: 1,
total_signers: 1,
timelock_blocks: 100,
emergency_admin: Some(Addr::unchecked("msg1admin000000000000000000000000000000000")),
};
GOVERNANCE.save(deps.as_mut().storage, &gov).unwrap();
// 提交升级提案
let proposer = Addr::unchecked("msg1dao0000000000000000000000000000000000000");
let target = Addr::unchecked("msg1target000000000000000000000000000000000");
let msg = to_binary(&"upgrade").unwrap();
let res = propose_upgrade(
deps.as_mut(),
env.clone(),
proposer,
target,
2,
msg,
100,
)
.unwrap();
assert_eq!(res.attributes[0].value, "propose_upgrade");
// 尝试在 Timelock 到期前执行(应在 100 区块后)
let executor = Addr::unchecked("msg1executor0000000000000000000000000000000");
let err = execute_timelocked_upgrade(
deps.as_mut(),
env.clone(),
executor,
1,
)
.unwrap_err();
assert!(err.to_string().contains("Timelock not expired"));
}
#[test]
fn test_emergency_rollback() {
let mut deps = mock_dependencies();
let env = mock_env();
// 设置紧急管理员
let gov = GovernanceConfig {
multisig_address: None,
dao_address: None,
upgrade_threshold: 1,
total_signers: 1,
timelock_blocks: 0,
emergency_admin: Some(Addr::unchecked("msg1admin000000000000000000000000000000000")),
};
GOVERNANCE.save(deps.as_mut().storage, &gov).unwrap();
// 保存回滚快照
deps.contract_info.code_id = 2;
deps.contract_info.version = "2.0.0".to_string();
save_rollback_snapshot(&mut deps.as_mut(), &env).unwrap();
// 验证快照
let emergency = EMERGENCY_STATE.load(&deps.storage).unwrap();
assert_eq!(emergency.previous_code_id, 2);
assert_eq!(emergency.previous_version, "2.0.0");
// 非管理员不能回滚
let attacker = Addr::unchecked("msg1attacker0000000000000000000000000000000");
let err = emergency_rollback(deps.as_mut(), env.clone(), attacker).unwrap_err();
assert!(err.to_string().contains("Unauthorized"));
}
#[test]
fn test_governance_access_control() {
let mut deps = mock_dependencies();
// 配置 DAO 治理
let gov = GovernanceConfig {
multisig_address: None,
dao_address: Some(Addr::unchecked("msg1daoaddr0000000000000000000000000000000")),
upgrade_threshold: 3,
total_signers: 5,
timelock_blocks: 1000,
emergency_admin: None,
};
GOVERNANCE.save(deps.as_mut().storage, &gov).unwrap();
// DAO 地址可以通过
let dao = Addr::unchecked("msg1daoaddr0000000000000000000000000000000");
assert!(validate_upgrade_authority(&deps.storage, &dao).is_ok());
// 随机地址不能通过
let random = Addr::unchecked("msg1random000000000000000000000000000000000");
assert!(validate_upgrade_authority(&deps.storage, &random).is_err());
}
}
附录
A. 完整项目结构
msg-upgradeable-contract/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── contract.rs # 合约入口
│ ├── msg.rs # 消息定义
│ ├── state.rs # 状态定义
│ ├── migration.rs # 迁移逻辑
│ ├── proxy.rs # 代理模式
│ ├── factory.rs # 工厂模式
│ ├── governance.rs # 治理控制
│ ├── timelock.rs # Timelock
│ └── testing.rs # 测试
├── examples/
│ ├── schema.rs
│ └── multi_version.rs
└── tests/
├── integration.rs
└── migration_tests.rs
B. Cargo.toml 依赖
[package]
name = "msg-upgradeable-contract"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
library = []
[dependencies]
cosmwasm-std = "1.5"
cosmwasm-storage = "1.5"
cw-storage-plus = "1.2"
cw-utils = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde-json-wasm = "0.5"
thiserror = "1.0"
schemars = "0.8"
semver = "1.0" # 可选,用于版本比较
[dev-dependencies]
cosmwasm-schema = "1.5"
C. MSG Chain 部署命令
# 编译
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.12.13
# 存储合约
msgd tx wasm store ./artifacts/msg_upgradeable_contract.wasm \
--from mywallet \
--gas auto \
--gas-adjustment 1.3 \
--chain-id msg-chain-1 \
-y
# 实例化
msgd tx wasm instantiate <CODE_ID> '{"owner":"msg1..."}' \
--from mywallet \
--label "upgradeable-v1" \
--admin msg1admin... \
--chain-id msg-chain-1 \
-y
# 迁移
msgd tx wasm migrate <CONTRACT_ADDR> <NEW_CODE_ID> '{"version":"2.0.0"}' \
--from mywallet \
--chain-id msg-chain-1 \
-y
# 查询
msgd query wasm contract <CONTRACT_ADDR>
msgd query wasm contract-state smart <CONTRACT_ADDR> '{"get_config":{}}'
D. 迁移清单
□ 部署前:
□ 设置合约管理员(Admin)
□ 记录初始 Code ID 和版本号
□ 部署监控脚本
□ 升级前:
□ 冻结新操作(暂停合约)
□ 备份当前状态(快照)
□ 测试新代码的迁移路径
□ 审查迁移对用户的影响
□ 升级中:
□ 分批次迁移用户数据
□ 监控 Gas 消耗
□ 记录每个迁移步骤
□ 升级后:
□ 验证状态完整性
□ 运行集成测试
□ 解冻合约操作
□ 通知用户
□ 更新文档
文档版本: 1.0.0
适用链: MSG Chain (msg-chain-1)
地址前缀:msg
