Rust智能合约开发库cw3的使用,cw3提供CosmWasm多签合约功能与区块链交互支持

Rust智能合约开发库cw3的使用,cw3提供CosmWasm多签合约功能与区块链交互支持

CW3规范:多签/投票合约

CW3是基于CosmWasm的投票合约规范,是CW1规范的扩展(CW1提供了直接的1/N多签功能)。在CW3中,没有任何密钥可以立即执行操作,只能提出一组消息的执行建议。提案、后续批准和签名聚合都在链上进行。

CW3规范至少涵盖以下三种用例:

  1. K/N不可变多签:一个密钥提出一组消息,在K-1个其他密钥批准后,可以使用多签地址执行。
  2. K/N灵活可变多签:与上类似,但有多个合约。一个合约存储组信息,被多个多签合约引用(这些多签合约又实现cw3)。一个cw3合约能够更新组内容(可能需要67%投票)。其他cw3合约可能持有代币、质押权等,具有各种执行阈值,都由一个组控制。
  3. 代币加权投票:人们将代币锁定在合约中获得投票权。执行消息有投票阈值。投票集是动态的。

基础接口

所有cw3合约必须实现以下接口。

消息

  1. Propose{title, description, msgs, earliest, latest} - 接受Vec<CosmosMsg>并创建新提案。返回自动生成的ID。
  2. Vote{proposal_id, vote} - 对提案投票,可选yes/no/abstain/veto。
  3. Execute{proposal_id} - 检查投票条件是否满足,如果满足则执行提案。
  4. Close{proposal_id} - 检查提案是否失败,如果失败则标记为Failed。

查询

  1. Threshold{} - 返回成功执行合约所需的规则。
  2. Proposal{proposal_id} - 返回提案信息及当前状态。
  3. ListProposals{start_after, limit} - 返回所有提案信息,分页。
  4. ReverseProposals{start_before, limit} - 反向返回所有提案信息,分页。
  5. Vote{proposal_id, voter} - 返回指定投票者对提案的投票情况。
  6. ListVotes{proposal_id, start_after, limit} - 返回提案的所有投票信息,分页。

投票者信息

  1. Voter { address } - 返回地址的投票权重(如果有)
  2. ListVoters { start_after, limit } - 列出所有合格投票者

完整示例代码

use cosmwasm_std::{CosmosMsg, Response, StdResult};
use cw3::{Proposal, ProposalResponse, Vote, VoteInfo, VoteResponse};

// 创建提案示例
pub fn propose(
    title: String,
    description: String,
    msgs: Vec<CosmosMsg>,
    earliest: Option<u64>,
    latest: Option<u64>,
) -> StdResult<Response> {
    let proposal = Proposal {
        title,
        description,
        msgs,
        earliest,
        latest,
    };
    
    // 这里通常会调用cw3合约的propose方法
    // 伪代码示例:
    // let res = cw3_proxy.propose(proposal)?;
    
    Ok(Response::new()
        .add_attribute("action", "propose")
        .add_attribute("status", "pending"))
}

// 投票示例
pub fn vote(proposal_id: u64, vote: Vote) -> StdResult<Response> {
    // 伪代码示例:
    // let res = cw3_proxy.vote(proposal_id, vote)?;
    
    Ok(Response::new()
        .add_attribute("action", "vote")
        .add_attribute("proposal_id", proposal_id.to_string())
        .add_attribute("vote", vote.to_string()))
}

// 执行提案示例
pub fn execute(proposal_id: u64) -> StdResult<Response> {
    // 伪代码示例:
    // let res = cw3_proxy.execute(proposal_id)?;
    
    Ok(Response::new()
        .add_attribute("action", "execute")
        .add_attribute("proposal_id", proposal_id.to_string()))
}

// 查询提案示例
pub fn query_proposal(proposal_id: u64) -> StdResult<ProposalResponse> {
    // 伪代码示例:
    // let res = cw3_proxy.query_proposal(proposal_id)?;
    
    Ok(ProposalResponse {
        id: proposal_id,
        proposal: Proposal {
            title: "Test Proposal".to_string(),
            description: "This is a test proposal".to_string(),
            msgs: vec![],
            earliest: None,
            latest: None,
        },
        status: "pending".to_string(),
        votes: vec![],
    })
}

// 查询投票示例
pub fn query_vote(proposal_id: u64, voter: String) -> StdResult<VoteResponse> {
    // 伪代码示例:
    // let res = cw3_proxy.query_vote(proposal_id, voter)?;
    
    Ok(VoteResponse {
        vote: Some(VoteInfo {
            proposal_id,
            voter,
            vote: Vote::Yes,
            weight: 1,
        }),
    })
}

安装

安装cw3作为库:

cargo add cw3

或者在Cargo.toml中添加:

cw3 = "2.0.0"

元数据

  • 许可证:Apache-2.0
  • 版本:2.0.0
  • 发布时间:超过1年前
  • 大小:15.7 KiB

完整Demo示例

下面是一个完整的CW3多签合约实现示例:

use cosmwasm_std::{
    entry_point, Binary, Deps, DepsMut, Env, MessageInfo, 
    Response, StdResult, to_binary, CosmosMsg
};
use cw3::{
    Vote, VoteInfo, VoteResponse, Proposal, ProposalResponse,
    Status, ThresholdResponse, VoterDetail, VoterListResponse
};
use cw_utils::Expiration;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// 定义合约状态
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
    pub threshold: u64,  // 通过提案所需的最小票数
    pub voters: Vec<VoterDetail>,  // 投票者列表及其权重
    pub max_voting_period: u64,  // 最大投票周期(秒)
}

// 初始化消息
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
    pub threshold: u64,
    pub voters: Vec<VoterDetail>,
    pub max_voting_period: u64,
}

// 执行消息
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
    Propose {
        title: String,
        description: String,
        msgs: Vec<CosmosMsg>,
    },
    Vote {
        proposal_id: u64,
        vote: Vote,
    },
    Execute {
        proposal_id: u64,
    },
    Close {
        proposal_id: u64,
    },
}

// 查询消息
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    Threshold {},
    Proposal { proposal_id: u64 },
    ListProposals {
        start_after: Option<u64>,
        limit: Option<u32>,
    },
    Vote { proposal_id: u64, voter: String },
    ListVotes {
        proposal_id: u64,
        start_after: Option<String>,
        limit: Option<u32>,
    },
    Voter { address: String },
    ListVoters {
        start_after: Option<String>,
        limit: Option<u32>,
    },
}

// 初始化合约
#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: InitMsg,
) -> StdResult<Response> {
    let state = State {
        threshold: msg.threshold,
        voters: msg.voters,
        max_voting_period: msg.max_voting_period,
    };
    
    // 存储状态
    deps.storage.set(b"state", &to_binary(&state)?);
    
    Ok(Response::default())
}

// 执行合约方法
#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    match msg {
        ExecuteMsg::Propose {
            title,
            description,
            msgs,
        } => execute_propose(deps, env, info, title, description, msgs),
        ExecuteMsg::Vote { proposal_id, vote } => {
            execute_vote(deps, env, info, proposal_id, vote)
        }
        ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id),
        ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id),
    }
}

// 查询合约方法
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::Threshold {} => to_binary(&query_threshold(deps)?),
        QueryMsg::Proposal { proposal_id } => {
            to_binary(&query_proposal(deps, env, proposal_id)?)
        }
        QueryMsg::ListProposals {
            start_after,
            limit,
        } => to_binary(&query_list_proposals(deps, env, start_after, limit)?),
        QueryMsg::Vote { proposal_id, voter } => {
            to_binary(&query_vote(deps, proposal_id, voter)?)
        }
        QueryMsg::ListVotes {
            proposal_id,
            start_after,
            limit,
        } => to_binary(&query_list_votes(deps, proposal_id, start_after, limit)?),
        QueryMsg::Voter { address } => to_binary(&query_voter(deps, address)?),
        QueryMsg::ListVoters { start_after, limit } => {
            to_binary(&query_list_voters(deps, start_after, limit)?)
        }
    }
}

// 实现提案创建函数
fn execute_propose(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    title: String,
    description: String,
    msgs: Vec<CosmosMsg>,
) -> StdResult<Response> {
    // 检查发送者是否有投票权
    let state = deps.storage.get(b"state").unwrap();
    let state: State = from_binary(&state)?;
    
    let has_voting_power = state.voters.iter().any(|v| v.addr == info.sender);
    if !has_voting_power {
        return Err(StdError::unauthorized());
    }
    
    // 创建新提案
    let proposal = Proposal {
        title,
        description,
        msgs,
        earliest: None,
        latest: Some(env.block.time.plus_seconds(state.max_voting_period)),
    };
    
    // 存储提案...
    
    Ok(Response::new().add_attribute("action", "propose"))
}

// 其他实现函数(execute_vote, execute_execute等)类似...

1 回复

以下是基于您提供内容的完整示例demo,展示了cw3多签合约的完整实现和使用:

use cosmwasm_std::{
    Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
    entry_point
};
use cw3::{
    Cw3Contract, ExecuteMsg, InstantiateMsg, Member, ProposalResponse, Threshold, Vote, ContractError
};
use cw_utils::Duration;

// 合约入口点
#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    let contract = Cw3Contract::default();
    contract.instantiate(deps, msg)
}

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    let contract = Cw3Contract::default();
    contract.execute(deps, env, info, msg)
}

#[entry_point]
pub fn query(
    deps: cosmwasm_std::Deps,
    env: Env,
    msg: cw3::QueryMsg,
) -> StdResult<ProposalResponse> {
    let contract = Cw3Contract::default();
    contract.query(deps, env, msg)
}

// 测试用例
#[cfg(test)]
mod tests {
    use super::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{coin, from_binary};

    // 测试完整的多签流程
    #[test]
    fn test_multisig_flow() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        let creator_info = mock_info("creator", &[]);
        
        // 1. 初始化多签钱包
        let members = vec![
            Member {
                addr: "member1".to_string(),
                weight: 1,
            },
            Member {
                addr: "member2".to_string(),
                weight: 1,
            },
            Member {
                addr: "member3".to_string(),
                weight: 1,
            },
        ];
        
        let init_msg = InstantiateMsg {
            voters: members,
            threshold: Threshold::AbsoluteCount { weight: 2 }, // 需要2票通过
            max_voting_period: Duration::Height(100),
            executor: None,
            title: "Team Wallet".to_string(),
            description: Some("Team operational funds".to_string()),
        };
        
        instantiate(deps.as_mut(), env.clone(), creator_info, init_msg).unwrap();
        
        // 2. 成员1创建转账提案
        let proposal_info = mock_info("member1", &[]);
        let transfer_msg = CosmosMsg::Bank(BankMsg::Send {
            to_address: "vendor".to_string(),
            amount: vec![coin(500, "ucosm")],
        });
        
        let propose_msg = ExecuteMsg::Propose {
            title: "Pay vendor invoice".to_string(),
            description: "Payment for services rendered".to_string(),
            msgs: vec![transfer_msg],
            latest: None,
        };
        
        execute(deps.as_mut(), env.clone(), proposal_info, propose_msg).unwrap();
        
        // 3. 查询提案状态
        let query_msg = cw3::QueryMsg::Proposal { proposal_id: 1 };
        let res: ProposalResponse = from_binary(
            &query(deps.as_ref(), env.clone(), query_msg).unwrap()
        ).unwrap();
        
        assert_eq!(res.proposal.status, cw3::Status::Open);
        
        // 4. 成员2投票赞成
        let vote_info = mock_info("member2", &[]);
        let vote_msg = ExecuteMsg::Vote {
            proposal_id: 1,
            vote: Vote::Yes,
        };
        
        execute(deps.as_mut(), env.clone(), vote_info, vote_msg).unwrap();
        
        // 5. 执行通过的提案
        let exec_info = mock_info("member3", &[]); // 任何人都可以执行
        let exec_msg = ExecuteMsg::Execute { proposal_id: 1 };
        
        let res = execute(deps.as_mut(), env, exec_info, exec_msg).unwrap();
        
        // 验证执行结果包含银行转账消息
        assert_eq!(res.messages.len(), 1);
        if let CosmosMsg::Bank(BankMsg::Send { to_address, amount }) = &res.messages[0].msg {
            assert_eq!(to_address, "vendor");
            assert_eq!(amount, &vec![coin(500, "ucosm")]);
        } else {
            panic!("Expected bank send message");
        }
    }
    
    // 测试自定义阈值类型
    #[test]
    fn test_threshold_types() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        
        // 测试绝对百分比阈值 (66%)
        let members = (1..=3).map(|i| Member {
            addr: format!("member{}", i),
            weight: 1,
        }).collect();
        
        let init_msg = InstantiateMsg {
            voters: members,
            threshold: Threshold::AbsolutePercentage {
                percentage: "0.66".parse().unwrap(),
            },
            max_voting_period: Duration::Height(100),
            executor: None,
            title: "DAO Treasury".to_string(),
            description: None,
        };
        
        instantiate(deps.as_mut(), env, mock_info("creator", &[]), init_msg).unwrap();
    }
}

这个完整示例展示了:

  1. 完整的合约入口点实现
  2. 多签钱包的初始化配置
  3. 提案创建、投票和执行的全流程
  4. 包含测试用例验证功能
  5. 支持不同类型的阈值配置

您可以根据实际需求调整成员列表、投票阈值和提案内容。这个示例可以直接用于CosmWasm兼容的区块链上的多签合约开发。

回到顶部