Rust智能合约开发库cw4-group的使用,CosmWasm生态成员组管理与权限控制插件

CW4 Group

这是一个基本实现cw4规范的合约。它满足了规范的所有要求,包括原始查询功能,并设计为作为cw3兼容合约的后端存储使用。

它存储了一组成员以及一个管理员,并允许管理员更新状态。原始查询(用于跨合约查询)可以检查给定的成员地址和总权重。智能查询(设计用于客户端API)可以做同样的事情,还可以查询管理员地址以及分页查询所有成员。

初始化

创建时需要传入成员列表,以及一个可选的admin(如果你希望它是可变的)。

pub struct InitMsg {
    pub admin: Option<HumanAddr>,
    pub members: Vec<Member>,
}

pub struct Member {
    pub addr: HumanAddr,
    pub weight: u64,
}

成员由地址和权重定义。这些会被转换并存储在其CanonicalAddr下,格式由cw4原始查询定义。

注意0是一个允许的权重。这不会给予任何投票权,但确实定义了该地址是组的一部分。这可用于例如KYC白名单中,表示他们被允许但不能参与决策。

消息

基本更新消息、查询和钩子由cw4规范定义。请参考它获取更多信息。

cw4-group添加了一个消息来控制组成员:

UpdateMembers{add, remove} - 接受一个成员差异并添加/更新成员,同时移除任何提供的地址。如果一个地址在两个列表中,它将被移除。如果它在add中出现多次,只有最后一次出现会被使用。

完整示例代码

use cosmwasm_std::{HumanAddr, Response, StdResult};
use cw4::{Member, MemberListResponse, MemberResponse, TotalWeightResponse};
use cw4_group::msg::{InitMsg, HandleMsg, QueryMsg};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// 初始化合约
pub fn init(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InitMsg,
) -> StdResult<Response> {
    // 验证管理员地址
    let admin = msg.admin
        .map(|a| deps.api.canonical_address(&a))
        .transpose()?;
    
    // 存储管理员
    if let Some(admin) = &admin {
        ADMIN.set(deps.storage, admin)?;
    }
    
    // 存储成员
    let members = msg.members
        .into_iter()
        .map(|m| {
            let addr = deps.api.canonical_address(&m.addr)?;
            Ok(Member {
                addr,
                weight: m.weight,
            })
        })
        .collect::<StdResult<Vec<_>>>()?;
    
    MEMBERS.save(deps.storage, &members)?;
    
    Ok(Response::default())
}

// 处理更新成员消息
pub fn handle_update_members(
    deps: DepsMut,
    info: MessageInfo,
    add: Vec<Member>,
    remove: Vec<HumanAddr>,
) -> StdResult<Response> {
    // 检查调用者是否是管理员
    ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
    
    // 加载现有成员
    let mut members = MEMBERS.load(deps.storage)?;
    
    // 移除成员
    for addr in remove {
        let addr = deps.api.canonical_address(&addr)?;
        members.retain(|m| m.addr != addr);
    }
    
    // 添加/更新成员
    for member in add {
        let addr = deps.api.canonical_address(&member.addr)?;
        // 如果存在则更新,否则添加
        if let Some(existing) = members.iter_mut().find(|m| m.addr == addr) {
            existing.weight = member.weight;
        else {
            members.push(Member {
                addr,
                weight: member.weight,
            });
        }
    }
    
    // 保存更新后的成员列表
    MEMBERS.save(deps.storage, &members)?;
    
    Ok(Response::default())
}

// 查询成员列表
pub fn query_members(
    deps: Deps,
    start_after: Option<HumanAddr>,
    limit: Option<u32>,
) -> StdResult<MemberListResponse> {
    // 读取所有成员
    let members = MEMBERS.load(deps.storage)?;
    
    // 处理分页
    let start = start_after
        .map(|addr| deps.api.canonical_address(&addr))
        .transpose()?;
    
    let members = members
        .into_iter()
        .filter(|m| start.as_ref().map_or(true, |s| m.addr > *s))
        .take(limit.unwrap_or(DEFAULT_LIMIT) as usize)
        .map(|m| {
            let addr = deps.api.human_address(&m.addr)?;
            Ok(Member {
                addr,
                weight: m.weight,
            })
        })
        .collect::<StdResult<Vec<_>>>()?;
    
    Ok(MemberListResponse { members })
}

// 查询单个成员
pub fn query_member(
    deps: Deps,
    addr: HumanAddr,
) -> StdResult<MemberResponse> {
    let addr = deps.api.canonical_address(&addr)?;
    let members = MEMBERS.load(deps.storage)?;
    
    let member = members
        .into_iter()
        .find(|m| m.addr == addr)
        .map(|m| {
            let addr = deps.api.human_address(&m.addr)?;
            Ok(Member {
                addr,
                weight: m.weight,
            })
        })
        .transpose()?;
    
    Ok(MemberResponse { weight: member.map(|m| m.weight) })
}

// 查询总权重
pub fn query_total_weight(
    deps: Deps,
) -> StdResult<TotalWeightResponse> {
    let members = MEMBERS.load(deps.storage)?;
    let total = members.into_iter().map(|m| m.weight).sum();
    Ok(TotalWeightResponse { weight: total })
}

完整Demo示例

use cosmwasm_std::{testing::{mock_dependencies, mock_env, mock_info}, Addr, HumanAddr};
use cw4::{Member, TotalWeightResponse};
use cw4_group::msg::{InitMsg, HandleMsg, QueryMsg};

#[test]
fn test_cw4_group_workflow() {
    // 初始化mock环境
    let mut deps = mock_dependencies();
    let env = mock_env();
    let info = mock_info("creator", &[]);
    
    // 准备初始化消息
    let init_msg = InitMsg {
        admin: Some(HumanAddr::from("admin")),
        members: vec![
            Member {
                addr: HumanAddr::from("member1"),
                weight: 1,
            },
            Member {
                addr: HumanAddr::from("member2"),
                weight: 2,
            }
        ],
    };
    
    // 初始化合约
    let res = init(deps.as_mut(), env.clone(), info, init_msg).unwrap();
    assert_eq!(0, res.messages.len());
    
    // 测试查询总权重
    let res: TotalWeightResponse =
        query_total_weight(deps.as_ref()).unwrap();
    assert_eq!(3, res.weight); // 1 + 2 = 3
    
    // 测试查询成员
    let res = query_member(
        deps.as_ref(), 
        HumanAddr::from("member1")
    ).unwrap();
    assert_eq!(Some(1), res.weight);
    
    // 准备更新成员的消息
    let info = mock_info("admin", &[]);
    let handle_msg = HandleMsg::UpdateMembers {
        add: vec![
            Member {
                addr: HumanAddr::from("member1"),
                weight: 3, // 更新权重
            },
            Member {
                addr: HumanAddr::from("member3"),
                weight: 4, // 新增成员
            }
        ],
        remove: vec![HumanAddr::from("member2")], // 移除成员
    };
    
    // 执行更新成员操作
    let res = handle_update_members(
        deps.as_mut(),
        info,
        handle_msg.add,
        handle_msg.remove,
    ).unwrap();
    assert_eq!(0, res.messages.len());
    
    // 验证更新后的总权重
    let res: TotalWeightResponse = 
        query_total_weight(deps.as_ref()).unwrap();
    assert_eq!(7, res.weight); // 3 (member1) + 4 (member3) = 7
    
    // 验证分页查询成员
    let res = query_members(
        deps.as_ref(),
        None,
        Some(10),
    ).unwrap();
    assert_eq!(2, res.members.len());
    assert_eq!(3, res.members[0].weight); // member1
    assert_eq!(4, res.members[1].weight); // member3
}

1 回复

Rust智能合约开发库cw4-group的使用:CosmWasm生态成员组管理与权限控制插件

简介

cw4-group是CosmWasm生态系统中一个用于成员组管理和权限控制的智能合约库。它实现了cw4规范,为区块链应用提供了灵活的用户组管理功能,特别适合需要权限控制的去中心化应用场景。

主要功能

  1. 成员组管理(添加/删除成员)
  2. 成员权重分配
  3. 基于权重的投票机制支持
  4. 与其他cw4兼容合约的互操作性

完整示例代码

下面是一个完整的cw4-group合约实现示例:

use cosmwasm_std::{
    Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, to_binary
};
use cw4_group::{
    msg::{InstantiateMsg, ExecuteMsg, QueryMsg, Member, MemberListResponse},
    ContractError
};

// 合约初始化
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    // 设置管理员,如果未指定则使用调用者地址
    let admin = msg.admin.unwrap_or_else(|| info.sender.clone());
    // 获取初始成员列表,如果没有则为空
    let members = msg.members.unwrap_or_default();
    
    // 初始化成员组
    cw4_group::initialize(deps.storage, admin, members)?;
    
    Ok(Response::default()
        .add_attribute("method", "instantiate")
        .add_attribute("admin", info.sender))
}

// 合约执行逻辑
pub fn execute(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::AddMember { addr, weight } => {
            // 验证调用者有管理员权限
            cw4_group::assert_admin(deps.as_ref(), &info.sender)?;
            
            // 添加新成员
            cw4_group::add_member(deps.storage, &addr, weight)?;
            
            Ok(Response::new()
                .add_attribute("action", "add_member")
                .add_attribute("addr", addr)
                .add_attribute("weight", weight.to_string()))
        }
        ExecuteMsg::RemoveMember { addr } => {
            // 验证调用者有管理员权限
            cw4_group::assert_admin(deps.as_ref(), &info.sender)?;
            
            // 移除成员
            cw4_group::remove_member(deps.storage, &addr)?;
            
            Ok(Response::new()
                .add_attribute("action", "remove_member")
                .add_attribute("addr", addr))
        }
        ExecuteMsg::UpdateAdmin { admin } => {
            // 验证调用者有管理员权限
            cw4_group::assert_admin(deps.as_ref(), &info.sender)?;
            
            // 更新管理员
            cw4_group::update_admin(deps.storage, admin)?;
            
            Ok(Response::new()
                .add_attribute("action", "update_admin")
                .add_attribute("new_admin", admin))
        }
    }
}

// 合约查询逻辑
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::Admin {} => {
            let admin = cw4_group::query_admin(deps.storage)?;
            to_binary(&admin)
        }
        QueryMsg::Member { addr } => {
            let member = cw4_group::query_member(deps.storage, &addr)?;
            to_binary(&member)
        }
        QueryMsg::ListMembers { start_after, limit } => {
            let members = cw4_group::list_members(deps.storage, start_after, limit)?;
            let resp = MemberListResponse { members };
            to_binary(&resp)
        }
        QueryMsg::TotalWeight {} => {
            let weight = cw4_group::query_total_weight(deps.storage)?;
            to_binary(&weight)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, Addr};

    #[test]
    fn test_instantiate() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        let info = mock_info("creator", &[]);
        
        // 初始化消息
        let init_msg = InstantiateMsg {
            admin: Some("admin".to_string()),
            members: vec![
                Member {
                    addr: "member1".to_string(),
                    weight: 10,
                },
                Member {
                    addr: "member2".to_string(),
                    weight: 20,
                },
            ],
        };
        
        // 执行初始化
        let res = instantiate(deps.as_mut(), env, info, init_msg).unwrap();
        assert_eq!(0, res.messages.len());
        
        // 查询管理员
        let admin_query = QueryMsg::Admin {};
        let bin = query(deps.as_ref(), mock_env(), admin_query).unwrap();
        let admin: Addr = from_binary(&bin).unwrap();
        assert_eq!(admin, Addr::unchecked("admin"));
        
        // 查询成员1
        let member_query = QueryMsg::Member { addr: "member1".to_string() };
        let bin = query(deps.as_ref(), mock_env(), member_query).unwrap();
        let weight: u64 = from_binary(&bin).unwrap();
        assert_eq!(weight, 10);
        
        // 查询总权重
        let total_query = QueryMsg::TotalWeight {};
        let bin = query(deps.as_ref(), mock_env(), total_query).unwrap();
        let total: u64 = from_binary(&bin).unwrap();
        assert_eq!(total, 30);
    }
}

最佳实践

  1. 权限控制:确保敏感操作(如添加/删除成员)有适当的权限检查
  2. 权重设计:合理分配成员权重以反映其在组织中的重要性
  3. 查询优化:对于大型成员组,使用分页查询(start_after和limit参数)
  4. 事件记录:为成员变更操作添加事件记录以便追踪

注意事项

  1. cw4-group存储结构简单,不适合超大规模成员组(成千上万成员)
  2. 成员权重是u64类型,确保不会溢出
  3. 合约升级需要考虑成员数据的迁移

cw4-group为CosmWasm生态系统提供了一个简单而强大的成员管理基础组件,可以轻松集成到各种需要权限管理的去中心化应用中。

回到顶部