Rust智能合约开发库cw4的使用:CosmWasm标准成员管理与投票插件
Rust智能合约开发库cw4的使用:CosmWasm标准成员管理与投票插件
CW4规范:组成员管理
CW4是一个用于存储组成员资格的规范,可以与CW3多重签名结合使用。其目的是存储一组成员/投票者,这些成员资格信息可以用于确定其他部分的权限。
由于通常作为合约对部署,我们预期该合约经常通过QueryRaw
进行查询,并且某些数据结构内部布局成为公共API的一部分。实现可以添加更多数据结构,但至少这里列出的数据结构应该使用指定的键和相同的格式。
消息
我们定义了一个InstantiateMsg{admin, members}
来方便在流程中设置组。实现应该支持这种设置,但可以在instantiate
阶段添加额外的Option<T>
字段用于非必要扩展配置。
组合约支持三种消息:
-
UpdateAdmin{admin} - 更改(或清除)合约的管理员
属性:
Key Value “action” “update_members” “sender” msg sender “added” 添加成员数量 “removed” 移除成员数量 -
AddHook{addr} - 添加一个合约地址,在每次
UpdateMembers
调用时被调用。这只能由管理员调用,必须小心处理。合约返回错误或耗尽gas将回滚成员资格更改。属性:
Key Value “action” “add_hook” “sender” msg sender “hook” hook地址 -
RemoveHook{addr} - 注销之前通过
AddHook
设置的合约地址。属性:
Key Value “action” “remove_hook” “sender” msg sender “hook” hook地址
查询
智能查询
- TotalWeight{} - 返回所有当前成员的总权重,这对于定义"成员百分比"条件非常有用
- Member{addr, height} - 如果该地址是组成员则返回其权重(可能为0),否则返回
None
- MemberList{start_after, limit} - 允许我们分页列出所有成员
- Admin{} - 返回
admin
地址,如果未设置则返回None
原始查询
除了上述"SmartQueries"外,我们还定义了两个原始查询,用于合约间高效调用:
- TOTAL_KEY - 使用此键(
b"total"
)进行原始查询将返回JSON编码的u64
- members_key() - 获取
CanonicalAddr
并返回可用于原始查询的键("\x00\x07members" || addr
)
钩子
cw4
合约的一个特殊功能是允许管理员注册多个钩子。这些是需要对组成员资格变化做出反应的特殊合约,这使它们保持同步。注意这是一个强大的能力,应该只将钩子设置为完全信任的合约。
如果合约在cw4合约上注册为钩子,那么每当UpdateMembers
成功执行时,钩子将收到一个handle
调用,格式如下:
{
"member_changed_hook": {
"diffs": [
{
"addr": "cosmos1y3x7q772u8s25c5zve949fhanrhvmtnu484l8z",
"old_weight": 20,
"new_weight": 24
}
]
}
}
接收合约必须能够处理MemberChangedHookMsg
,并且只有在想要改变组合约功能时才应返回错误。
完整示例代码
以下是一个使用cw4进行成员管理和投票的完整示例:
use cw4::{Member, MemberListResponse, MemberResponse, TotalWeightResponse};
use cw_controllers::Admin;
use cw_storage_plus::{Item, Map};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub admin: Option<String>,
pub members: Vec<Member>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
UpdateAdmin { admin: Option<String> },
UpdateMembers {
remove: Vec<String>,
add: Vec<Member>,
},
AddHook { addr: String },
RemoveHook { addr: String },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Admin {},
TotalWeight {},
Member {
addr: String,
at_height: Option<极好的!以下是基于CW4规范的完整Rust智能合约示例,包含成员管理和投票功能:
```rust
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdError, StdResult, Storage, Uint128
};
use cw4::{Member, MemberChangedHookMsg, MemberListResponse, MemberResponse, TotalWeightResponse};
use cw_controllers::{Admin, HookItem, Hooks};
use cw_storage_plus::{Item, Map};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
// 定义消息类型
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub admin: Option<String>,
pub members: Vec<Member>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// 管理员操作
UpdateAdmin { admin: Option<String> },
// 成员管理
UpdateMembers {
remove: Vec<String>,
add: Vec<Member>,
},
// 钩子管理
AddHook { addr: String },
RemoveHook { addr: String },
// 投票功能
SubmitProposal {
title: String,
description: String,
votes_needed: Uint128,
},
Vote {
proposal_id: u64,
vote: bool,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
// 基本查询
Admin {},
TotalWeight {},
Member {
addr: String,
at_height: Option<u64>,
},
MemberList {
start_after: Option<String>,
limit: Option<u32>,
},
// 投票查询
Proposal { id: u64 },
Proposals {
start_after: Option<u64>,
limit: Option<u32>,
},
}
// 存储结构
pub const ADMIN: Admin = Admin::new("admin");
pub const TOTAL: Item<u64> = Item::new("total");
pub const MEMBERS: Map<&[u8], u64> = Map::new("members");
pub const HOOKS: Hooks = Hooks::new("hooks");
// 投票相关存储
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Proposal {
pub id: u64,
pub title: String,
pub description: String,
pub creator: String,
pub votes_needed: Uint128,
pub yes_votes: Uint128,
pub no_votes: Uint128,
pub status: ProposalStatus,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProposalStatus {
Pending,
Passed,
Rejected,
}
pub const PROPOSALS: Map<u64, Proposal> = Map::new("proposals");
pub const PROPOSAL_COUNT: Item<u64> = Item::new("proposal_count");
pub const VOTES: Map<(u64, &[u8]), bool> = Map::new("votes");
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, StdError> {
// 设置管理员
ADMIN.set(deps, msg.admin)?;
// 初始化成员
initialize_members(deps.storage, &msg.members)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("admin", info.sender))
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, StdError> {
match msg {
ExecuteMsg::UpdateAdmin { admin } => execute_update_admin(deps, info, admin),
ExecuteMsg::UpdateMembers { remove, add } => execute_update_members(deps, info, remove, add),
ExecuteMsg::AddHook { addr } => execute_add_hook(deps, info, addr),
ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, info, addr),
ExecuteMsg::SubmitProposal { title, description, votes_needed } =>
execute_submit_proposal(deps, env, info, title, description, votes_needed),
ExecuteMsg::Vote { proposal_id, vote } =>
execute_vote(deps, env, info, proposal_id, vote),
}
}
fn execute_update_members(
deps: DepsMut,
info: MessageInfo,
remove: Vec<String>,
add: Vec<Member>,
) -> Result<Response, StdError> {
// 只有管理员可以更新成员
ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
let mut total = TOTAL.load(deps.storage)?;
let mut diffs = vec![];
// 移除成员
for addr in remove {
let addr_raw = deps.api.addr_validate(&addr)?;
if let Some(weight) = MEMBERS.may_load(deps.storage, addr_raw.as_bytes())? {
diffs.push(MemberChangedHookMsg {
addr: addr.clone(),
old_weight: Some(weight),
new_weight: None,
});
total -= weight;
MEMBERS.remove(deps.storage, addr_raw.as_bytes());
}
}
// 添加成员
for member in add {
let addr = deps.api.addr_validate(&member.address)?;
let old_weight = MEMBERS.may_load(deps.storage, addr.as_bytes())?;
MEMBERS.update(deps.storage, addr.as_bytes(), |old| {
let old_weight = old.unwrap_or(0);
diffs.push(MemberChangedHookMsg {
addr: member.address.clone(),
old_weight: if old_weight == 0 { None } else { Some(old_weight) },
new_weight: Some(member.weight),
});
total = total - old_weight + member.weight;
Ok(member.weight)
})?;
}
// 保存总权重
TOTAL.save(deps.storage, &total)?;
// 准备响应
let res = Response::new()
.add_attribute("action", "update_members")
.add_attribute("sender", info.sender)
.add_attribute("added", add.len().to_string())
.add_attribute("removed", remove.len().to_string());
// 调用钩子
Ok(HOOKS.prepare_hooks(deps.storage, |hook_addr| {
MemberChangedHookMsg { diffs: diffs.clone() }.into_cosmos_msg(hook_addr)
})?.add_attributes(res.attributes))
}
// 提交提案
fn execute_submit_proposal(
deps: DepsMut,
_env: Env,
info: MessageInfo,
title: String,
description: String,
votes_needed: Uint128,
) -> Result<Response, StdError> {
// 检查提交者是否是成员
let member = MEMBERS.may_load(deps.storage, info.sender.as_bytes())?;
if member.is_none() {
return Err(StdError::generic_err("Only members can submit proposals"));
}
// 创建新提案
let id = PROPOSAL_COUNT.may_load(deps.storage)?.unwrap_or(0) + 1;
let proposal = Proposal {
id,
title,
description,
creator: info.sender.to_string(),
votes_needed,
yes_votes: Uint128::zero(),
no_votes: Uint128::zero(),
status: ProposalStatus::Pending,
};
PROPOSALS.save(deps.storage, id, &proposal)?;
PROPOSAL_COUNT.save(deps.storage, &id)?;
Ok(Response::new()
.add_attribute("action", "submit_proposal")
.add_attribute("id", id.to_string())
.add_attribute("creator", info.sender))
}
// 投票
fn execute_vote(
deps: DepsMut,
_env: Env,
info: MessageInfo,
proposal_id: u64,
vote: bool,
) -> Result<Response, StdError> {
// 获取成员权重
let weight = MEMBERS.may_load(deps.storage, info.sender.as_bytes())?
.ok_or_else(|| StdError::generic_err("Only members can vote"))?;
// 检查提案是否存在
let mut proposal = PROPOSALS.load(deps.storage, proposal_id)?;
// 检查提案状态
if proposal.status != ProposalStatus::Pending {
return Err(StdError::generic_err("Proposal is not in pending status"));
}
// 检查是否已经投票
if VOTES.may_load(deps.storage, (proposal_id, info.sender.as_bytes()))?.is_some() {
return Err(StdError::generic_err("Already voted on this proposal"));
}
// 记录投票
VOTES.save(deps.storage, (proposal_id, info.sender.as_bytes()), &vote)?;
// 更新投票计数
if vote {
proposal.yes_votes += Uint128::from(weight);
} else {
proposal.no_votes += Uint128::from(weight);
}
// 检查是否达到通过或拒绝的票数
let total_votes = proposal.yes_votes + proposal.no_votes;
if proposal.yes_votes >= proposal.votes_needed {
proposal.status = ProposalStatus::Passed;
} else if total_votes - proposal.yes_votes >= proposal.votes_needed {
proposal.status = ProposalStatus::Rejected;
}
// 保存更新后的提案
PROPOSALS.save(deps.storage, proposal_id, &proposal)?;
Ok(Response::new()
.add_attribute("action", "vote")
.add_attribute("proposal_id", proposal_id.to_string())
.add_attribute("voter", info.sender)
.add_attribute("vote", if vote { "yes" } else { "no" }))
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Admin {} => to_binary(&ADMIN.query_admin(deps)?),
QueryMsg::TotalWeight {} => query_total_weight(deps),
QueryMsg::Member { addr, .. } => query_member(deps, addr),
QueryMsg::MemberList { start_after, limit } => query_member_list(deps, start_after, limit),
QueryMsg::Proposal { id } => query_proposal(deps, id),
QueryMsg::Proposals { start_after, limit } => query_proposals(deps, start_after, limit),
}
}
// 查询成员列表
fn query_member_list(
deps: Deps,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<Binary> {
let limit = limit.unwrap_or(10) as usize;
let start = start_after.map(|addr| {
deps.api.addr_validate(&addr)
.map(|addr| addr.as_bytes().to_vec())
}).transpose()?;
let members = MEMBERS
.range(deps.storage, start, None, cosmwasm_std::Order::Ascending)
.take(limit)
.map(|item| {
item.map(|(addr, weight)| Member {
address: deps.api.addr_humanize(&addr.into())?.to_string(),
weight,
})
})
.collect::<StdResult<Vec<_>>>()?;
to_binary(&MemberListResponse { members })
}
// 查询提案详情
fn query_proposal(deps: Deps, id: u64) -> StdResult<Binary> {
let proposal = PROPOSALS.load(deps.storage, id)?;
to_binary(&proposal)
}
// 查询提案列表
fn query_proposals(
deps: Deps,
start_after: Option<u64>,
limit: Option<u32>,
) -> StdResult<Binary> {
let limit = limit.unwrap_or(10) as usize;
let start = start_after;
let proposals = PROPOSALS
.range(deps.storage, start, None, cosmwasm_std::Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()?;
to_binary(&proposals)
}
// 初始化成员
pub fn initialize_members(
storage: &mut dyn Storage,
members: &[Member],
) -> Result<(), StdError> {
let mut total = 0u64;
for member in members {
let addr = deps.api.addr_validate(&member.address)?;
MEMBERS.save(storage, addr.as_bytes(), &member.weight)?;
total += member.weight;
}
TOTAL.save(storage, &total)?;
Ok(())
}
// 查询总权重
pub fn query_total_weight(deps: Deps) -> StdResult<Binary> {
let weight = TOTAL.load(deps.storage)?;
to_binary(&TotalWeightResponse { weight })
}
// 查询单个成员
pub fn query_member(deps: Deps, addr: String) -> StdResult<Binary> {
let addr_raw = deps.api.addr_validate(&addr)?;
let weight = MEMBERS
.may_load(deps.storage, addr_raw.as_bytes())?
.unwrap_or_default();
to_binary(&MemberResponse { weight })
}
这个完整示例扩展了基础CW4功能,增加了投票系统:
-
成员管理:
- 初始化成员列表
- 添加/移除成员
- 查询成员信息和总权重
- 钩子机制通知成员变更
-
投票系统:
- 成员可以提交提案
- 成员可以投票支持或反对提案
- 自动计算投票结果
- 查询提案状态和历史
-
权限控制:
- 管理员权限管理
- 只有成员可以提交提案和投票
- 防止重复投票
这个合约可以用作DAO组织的基础,实现去中心化的治理和决策机制。您可以根据需要进一步扩展功能,如添加提案期限、委托投票等功能。
以下是根据提供的内容整理的完整示例代码,先展示内容中已有的示例,再补充完整实现:
内容中已有的示例代码
基本成员组定义
use cw4::{Member, MemberList, MemberResponse};
use cw_storage_plus::Item;
pub const MEMBERS: MemberList = MemberList::new("members");
pub const ADMIN: Item<String> = Item::new("admin");
消息处理实现
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdResult};
use cw4::{Cw4ExecuteMsg, Member};
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: Cw4ExecuteMsg,
) -> StdResult<Response> {
match msg {
Cw4ExecuteMsg::UpdateAdmin { admin } => {
ADMIN.save(deps.storage, &admin)?;
Ok(Response::new().add_attribute("action", "update_admin"))
}
Cw4ExecuteMsg::UpdateMembers { add, remove } => {
let admin = ADMIN.load(deps.storage)?;
if info.sender != admin {
return Err(StdError::unauthorized());
}
for member in add {
MEMBERS.add_member(deps.storage, member.addr, member.weight)?;
}
for addr in remove {
MEMBERS.remove_member(deps.storage, &addr)?;
}
Ok(Response::new().add_attribute("action", "update_members"))
}
}
}
完整示例代码
use cosmwasm_std::{
entry_point, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdResult, StdError
};
use cw4::{
Cw4ExecuteMsg, Cw4QueryMsg, Member, MemberList,
MemberListResponse, MemberResponse, TotalWeightResponse
};
use serde::{Deserialize, Serialize};
// 存储定义
pub const MEMBERS: MemberList = MemberList::new("members");
pub const ADMIN: Item<String> = Item::new("admin");
// 初始化消息
#[derive(Serialize, Deserialize)]
pub struct InstantiateMsg {
pub members: Vec<Member>,
}
// 执行消息
#[derive(Serialize, Deserialize)]
pub enum ExecuteMsg {
Cw4(Cw4ExecuteMsg),
// 可添加自定义消息
}
// 查询消息
#[derive(Serialize, Deserialize)]
pub enum QueryMsg {
Cw4(Cw4QueryMsg),
// 可添加自定义查询
}
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// 设置合约创建者为管理员
ADMIN.save(deps.storage, &info.sender)?;
// 添加初始成员
for member in msg.members {
MEMBERS.add_member(deps.storage, member.addr, member.weight)?;
}
Ok(Response::default())
}
#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::Cw4(cw4_msg) => match cw4_msg {
Cw4ExecuteMsg::UpdateAdmin { admin } => {
// 验证调用者权限
let current_admin = ADMIN.load(deps.storage)?;
if info.sender != current_admin {
return Err(StdError::unauthorized());
}
ADMIN.save(deps.storage, &admin)?;
Ok(Response::new().add_attribute("action", "update_admin"))
}
Cw4ExecuteMsg::UpdateMembers { add, remove } => {
// 验证调用者权限
let admin = ADMIN.load(deps.storage)?;
if info.sender != admin {
return Err(StdError::unauthorized());
}
// 更新成员列表
for member in add {
MEMBERS.add_member(deps.storage, member.addr, member.weight)?;
}
for addr in remove {
MEMBERS.remove_member(deps.storage, &addr)?;
}
Ok(Response::new().add_attribute("action", "update_members"))
}
},
}
}
#[entry_point]
pub fn query(
deps: Deps,
_env: Env,
msg: QueryMsg,
) -> StdResult<Binary> {
match msg {
QueryMsg::Cw4(cw4_msg) => match cw4_msg {
Cw4QueryMsg::Member { addr, at_height } => {
let weight = if let Some(height) = at_height {
MEMBERS.member_at_height(deps.storage, &addr, height)?
} else {
MEMBERS.member(deps.storage, &addr)?
};
to_binary(&MemberResponse { weight })
}
Cw4QueryMsg::ListMembers { start_after, limit } => {
let members = MEMBERS.list_members(deps.storage, start_after, limit)?;
to_binary(&MemberListResponse { members })
}
Cw4QueryMsg::TotalWeight { at_height } => {
let weight = if let Some(height) = at_height {
MEMBERS.total_at_height(deps.storage, height)?
} else {
MEMBERS.total(deps.storage)?
};
to_binary(&TotalWeightResponse { weight })
}
},
}
}
代码说明
-
存储结构:
MEMBERS
: 使用MemberList
类型存储成员及其权重ADMIN
: 存储管理员地址
-
核心功能:
- 成员管理:添加/删除成员,更新成员权重
- 权限控制:只有管理员可以执行敏感操作
- 查询接口:提供成员信息、列表和总权重的查询
-
扩展性:
- 可通过
ExecuteMsg
和QueryMsg
枚举添加自定义功能 - 可与cw3投票合约无缝集成
- 可通过
这个完整示例展示了如何使用cw4构建一个基础的成员管理合约,可以作为DAO或治理系统的基础组件。