Rust智能合约开发库cw4的使用:CosmWasm标准成员管理与投票插件

Rust智能合约开发库cw4的使用:CosmWasm标准成员管理与投票插件

CW4规范:组成员管理

CW4是一个用于存储组成员资格的规范,可以与CW3多重签名结合使用。其目的是存储一组成员/投票者,这些成员资格信息可以用于确定其他部分的权限。

由于通常作为合约对部署,我们预期该合约经常通过QueryRaw进行查询,并且某些数据结构内部布局成为公共API的一部分。实现可以添加更多数据结构,但至少这里列出的数据结构应该使用指定的键和相同的格式。

消息

我们定义了一个InstantiateMsg{admin, members}来方便在流程中设置组。实现应该支持这种设置,但可以在instantiate阶段添加额外的Option<T>字段用于非必要扩展配置。

组合约支持三种消息:

  1. UpdateAdmin{admin} - 更改(或清除)合约的管理员

    属性:

    Key Value
    “action” “update_members”
    “sender” msg sender
    “added” 添加成员数量
    “removed” 移除成员数量
  2. AddHook{addr} - 添加一个合约地址,在每次UpdateMembers调用时被调用。这只能由管理员调用,必须小心处理。合约返回错误或耗尽gas将回滚成员资格更改。

    属性:

    Key Value
    “action” “add_hook”
    “sender” msg sender
    “hook” hook地址
  3. RemoveHook{addr} - 注销之前通过AddHook设置的合约地址。

    属性:

    Key Value
    “action” “remove_hook”
    “sender” msg sender
    “hook” hook地址

查询

智能查询

  1. TotalWeight{} - 返回所有当前成员的总权重,这对于定义"成员百分比"条件非常有用
  2. Member{addr, height} - 如果该地址是组成员则返回其权重(可能为0),否则返回None
  3. MemberList{start_after, limit} - 允许我们分页列出所有成员
  4. Admin{} - 返回admin地址,如果未设置则返回None

原始查询

除了上述"SmartQueries"外,我们还定义了两个原始查询,用于合约间高效调用:

  1. TOTAL_KEY - 使用此键(b"total")进行原始查询将返回JSON编码的u64
  2. 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功能,增加了投票系统:

  1. 成员管理

    • 初始化成员列表
    • 添加/移除成员
    • 查询成员信息和总权重
    • 钩子机制通知成员变更
  2. 投票系统

    • 成员可以提交提案
    • 成员可以投票支持或反对提案
    • 自动计算投票结果
    • 查询提案状态和历史
  3. 权限控制

    • 管理员权限管理
    • 只有成员可以提交提案和投票
    • 防止重复投票

这个合约可以用作DAO组织的基础,实现去中心化的治理和决策机制。您可以根据需要进一步扩展功能,如添加提案期限、委托投票等功能。


1 回复

以下是根据提供的内容整理的完整示例代码,先展示内容中已有的示例,再补充完整实现:

内容中已有的示例代码

基本成员组定义

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 })
            }
        },
    }
}

代码说明

  1. 存储结构

    • MEMBERS: 使用MemberList类型存储成员及其权重
    • ADMIN: 存储管理员地址
  2. 核心功能

    • 成员管理:添加/删除成员,更新成员权重
    • 权限控制:只有管理员可以执行敏感操作
    • 查询接口:提供成员信息、列表和总权重的查询
  3. 扩展性

    • 可通过ExecuteMsgQueryMsg枚举添加自定义功能
    • 可与cw3投票合约无缝集成

这个完整示例展示了如何使用cw4构建一个基础的成员管理合约,可以作为DAO或治理系统的基础组件。

回到顶部