diff --git a/Cargo.lock b/Cargo.lock index 28ec46c8d..f79e3aba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,6 +2928,7 @@ dependencies = [ name = "multisig-prover" version = "0.1.0" dependencies = [ + "anyhow", "axelar-wasm-std", "connection-router", "cosmwasm-schema", @@ -2935,9 +2936,11 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", "cw2 0.15.1", "either", "ethabi", + "gateway", "hex", "k256 0.13.1", "multisig", diff --git a/README.md b/README.md index 7f1926031..6ca360eec 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ P --"GetMessages([M1.id,M2.id])"-->G2 P --"GetActiveWorkers"-->S P --"StartSigningSession(key_id, batch_hash)"-->M Workers --"SubmitSignature(session_id, signature)"-->M -Relayer --"GetProof(proof_id)" --> P +Relayer --"GetProof(multisig_session_id)" --> P P --"GetSigningSession(session_id)"-->M ``` @@ -114,11 +114,11 @@ sequenceDiagram OutgoingGateway-->>Prover: [M1,M2] Prover->>Prover: create batch of [M1,M2] Prover->>Multisig: StartSigningSession(snapshot, batch hash) - Multisig-->>Prover: session_id - Prover-->>Relayer: proof_id + Multisig-->>Prover: multisig_session_id + Prover-->>Relayer: multisig_session_id Worker->>Multisig: SubmitSignature(session_id, signature) Worker->>Multisig: SubmitSignature(session_id, signature) - Relayer->>Prover: GetProof(proof_id) + Relayer->>Prover: GetProof(multisig_session_id) Prover->>Multisig: GetSigningSession(session_id) Multisig-->>Prover: signing session Prover-->>Relayer: signed batch @@ -144,8 +144,7 @@ The gateway also accepts messages from the router. These are messages sent from The verifier contracts are responsible for verifying whether a given message or batch of messages has occurred on a connected external chain. The verifier can take many different forms, such as a [`voting-verifier`](./contracts/voting-verifier) that conducts stake weighted polls for batches of messages, a light client that accepts block headers and merkle tree proofs, a zk proof verifier, etc. The verifier can also be an [`aggregate-verifier`](./contracts/aggregate-verifier), that is linked to 1 or more other verifiers, and defines a security policy such as 2 out of 3 linked verification methods need to report a message as verified. ### Prover -The prover contract is responsible for constructing proofs of routed messages, to be passed to external chains. The most common example of this is the [`multisig-prover`](./contracts/command-batcher) that constructs signed batches of routed messages, which are then relayed (permissionlessly) to an external chain. In this example, the prover fetches the messages from the gateway, and interacts with the multisig contract to conduct the signing, -The prover contract is responsible for constructing proofs of routed messages, to be passed to external chains. The most common example of this is the [`multisig-prover`](./contracts/multisig-prover) that constructs signed batches of routed messages, which are then relayed (permissionlessly) to an external chain. In this example, the prover fetches the messages from the gateway, and interacts with the multisig contract to conduct the signing, +The prover contract is responsible for constructing proofs of routed messages, to be passed to external chains. The most common example of this is the [`multisig-prover`](./contracts/multisig-prover) that constructs signed batches of routed messages, which are then relayed (permissionlessly) to an external chain. In this example, the prover fetches the messages from the gateway, and interacts with the multisig contract to conduct the signing. ### Multisig Contract [`multisig`](./contracts/multisig) is responsible for signing arbitrary blobs of data. Contracts register with the multisig contract to generate a key id, and then use that key id to initiate signing sessions. Off chain workers associated with the key id sign messages when new signing sessions are created. diff --git a/ampd/tests/report.rs b/ampd/tests/report.rs index dd2e22dfc..daf93d0dc 100644 --- a/ampd/tests/report.rs +++ b/ampd/tests/report.rs @@ -1,3 +1,5 @@ +use std::env; + use error_stack::Report; use ampd::report::{Error, LoggableError}; @@ -5,6 +7,7 @@ use ampd::report::{Error, LoggableError}; // Do not move this test or the location field checks break #[test] fn correct_error_log() { + env::set_var("RUST_BACKTRACE", "1"); let report = Report::new(Error::new("error1".to_string())) .attach_printable("foo1") .change_context(Error::new("error2".to_string())) @@ -25,15 +28,15 @@ fn correct_error_log() { let expected_err = LoggableError { msg: "error3".to_string(), attachments: vec!["opaque attachment".to_string()], - location: "ampd/tests/report.rs:13:10".to_string(), + location: "ampd/tests/report.rs:16:10".to_string(), cause: Some(Box::new(LoggableError { msg: "error2".to_string(), attachments: vec!["test1".to_string(), "test2".to_string()], - location: "ampd/tests/report.rs:10:10".to_string(), + location: "ampd/tests/report.rs:13:10".to_string(), cause: Some(Box::new(LoggableError { msg: "error1".to_string(), attachments: vec!["foo1".to_string()], - location: "ampd/tests/report.rs:8:18".to_string(), + location: "ampd/tests/report.rs:11:18".to_string(), cause: None, backtrace: None, })), diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 18405f9db..0e547f055 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -33,6 +33,7 @@ cosmwasm-schema = "1.1.3" cosmwasm-std = "1.1.3" cosmwasm-storage = "1.1.3" cw-storage-plus = "1.0.1" +cw-utils = "1.0.1" cw2 = "0.15.1" schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } @@ -47,6 +48,8 @@ multisig = { workspace = true, features = ["library"] } service-registry = { workspace = true, features = ["library"] } axelar-wasm-std = { workspace = true } k256 = { version = "0.13.1", features = ["ecdsa"] } +gateway = { workspace = true, features = ["library"]} [dev-dependencies] cw-multi-test = "0.15.1" +anyhow = "1.0" diff --git a/contracts/multisig-prover/README.md b/contracts/multisig-prover/README.md index 853f4a664..7b5ad8e64 100644 --- a/contracts/multisig-prover/README.md +++ b/contracts/multisig-prover/README.md @@ -88,7 +88,7 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { #[returns(GetProofResponse)] - GetProof { proof_id: String }, + GetProof { multisig_session_id: Uint64 }, } pub enum ProofStatus { @@ -97,32 +97,24 @@ pub enum ProofStatus { } pub struct GetProofResponse { - pub proof_id: HexBinary, + pub multisig_session_id: Uint64, pub message_ids: Vec, pub data: Data, - pub proof: Proof, pub status: ProofStatus, } - -pub struct Data { - pub destination_chain_id: Uint256, - pub commands_ids: Vec<[u8; 32]>, - pub commands_types: Vec, - pub commands_params: Vec -} - -pub struct Proof { - pub operators: Vec, - pub weights: Vec, - pub threshold: Uint256, - pub signatures: Vec, -} ``` ## Events ```Rust -pub struct ProofUnderConstruction { - pub proof_id: HexBinary, // Unique hash derived from the message ids +pub enum Event { + ProofUnderConstruction { + multisig_session_id: Uint64, + }, + SnapshotRotated { + key_id: String, + snapshot: Snapshot, + pub_keys: HashMap, + }, } ``` diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 628acddd9..69d07859a 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -1,13 +1,54 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, +}; + +use std::str::FromStr; + +use connection_router::types::ChainName; use crate::{ error::ContractError, + execute, msg::ExecuteMsg, - msg::{GetProofResponse, QueryMsg}, + msg::{InstantiateMsg, QueryMsg}, + query, reply, + state::{Config, CONFIG}, }; +pub const START_MULTISIG_REPLY_ID: u64 = 1; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let admin = deps.api.addr_validate(&msg.admin_address)?; + let gateway = deps.api.addr_validate(&msg.gateway_address)?; + let multisig = deps.api.addr_validate(&msg.multisig_address)?; + let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; + + let config = Config { + admin, + gateway, + multisig, + service_registry, + destination_chain_id: msg.destination_chain_id, + signing_threshold: msg.signing_threshold, + service_name: msg.service_name, + chain_name: ChainName::from_str(&msg.chain_name) + .map_err(|_| ContractError::InvalidChainName)?, + worker_set_diff_threshold: msg.worker_set_diff_threshold, + }; + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -16,120 +57,209 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::ConstructProof { message_ids } => execute::construct_proof(message_ids), + ExecuteMsg::ConstructProof { message_ids } => execute::construct_proof(deps, message_ids), ExecuteMsg::UpdateWorkerSet {} => execute::update_worker_set(deps, env), } } -pub mod execute { +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result { + match reply.id { + START_MULTISIG_REPLY_ID => reply::start_multisig_reply(deps, reply), + _ => unreachable!("unknown reply ID"), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetProof { + multisig_session_id, + } => to_binary(&query::get_proof(deps, multisig_session_id)?), + } +} - use axelar_wasm_std::snapshot; - use cosmwasm_std::{wasm_execute, QueryRequest, WasmQuery}; - use multisig::types::PublicKey; - use service_registry::state::Worker; +#[cfg(test)] +mod test { + + use anyhow::Error; + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + Addr, Fraction, Uint256, Uint64, + }; + use cw_multi_test::{AppResponse, Executor}; + use ethabi::{ParamType, Token}; use crate::{ - state::{WorkerSet, CONFIG, CURRENT_WORKER_SET, NEXT_WORKER_SET}, - types::CommandBatch, + msg::{GetProofResponse, ProofStatus}, + test::{ + multicontract::{setup_test_case, TestCaseConfig}, + test_data, + }, }; use super::*; - pub fn construct_proof(_message_ids: Vec) -> Result { - todo!() - } + const RELAYER: &str = "relayer"; + const MULTISIG_SESSION_ID: Uint64 = Uint64::one(); - pub fn update_worker_set(deps: DepsMut, env: Env) -> Result { - let config = CONFIG.load(deps.storage)?; + fn execute_key_gen(test_case: &mut TestCaseConfig) -> Result { + let msg = ExecuteMsg::UpdateWorkerSet {}; + test_case.app.execute_contract( + test_case.admin.clone(), + test_case.prover_address.clone(), + &msg, + &[], + ) + } - let active_workers_query = service_registry::msg::QueryMsg::GetActiveWorkers { - service_name: config.service_name, - chain_name: config.chain_name.into(), + fn execute_construct_proof( + test_case: &mut TestCaseConfig, + message_ids: Option>, + ) -> Result { + let message_ids = match message_ids { + Some(ids) => ids, + None => test_data::messages() + .into_iter() + .map(|msg| msg.id.to_string()) + .collect::>(), }; - let workers: Vec = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: config.service_registry.to_string(), - msg: to_binary(&active_workers_query)?, - }))?; - - let participants = workers - .clone() - .into_iter() - .map(service_registry::state::Worker::try_into) - .collect::, _>>()?; - - let snapshot = snapshot::Snapshot::new( - env.block.time.try_into()?, - env.block.height.try_into()?, - config.signing_threshold, - participants.try_into()?, - ); - let pub_keys_query = multisig::msg::QueryMsg::GetPublicKeys { - worker_addresses: workers.iter().map(|w| w.address.to_string()).collect(), - key_type: multisig::types::KeyType::ECDSA, + let msg = ExecuteMsg::ConstructProof { message_ids }; + test_case.app.execute_contract( + Addr::unchecked(RELAYER), + test_case.prover_address.clone(), + &msg, + &[], + ) + } + + fn query_get_proof( + test_case: &mut TestCaseConfig, + multisig_session_id: Option, + ) -> StdResult { + let multisig_session_id = match multisig_session_id { + Some(id) => id, + None => MULTISIG_SESSION_ID, }; - let pub_keys: Vec = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: config.multisig.to_string(), - msg: to_binary(&pub_keys_query)?, - }))?; - - let new_worker_set = WorkerSet::new(snapshot, pub_keys, env)?; - - let cur_worker_set = CURRENT_WORKER_SET.load(deps.storage)?; - - if !should_update_worker_set( - &new_worker_set, - &cur_worker_set, - config.worker_set_diff_threshold as usize, - ) { - return Err(ContractError::WorkerSetUnchanged); - } - NEXT_WORKER_SET.save(deps.storage, &new_worker_set)?; + test_case.app.wrap().query_wasm_smart( + test_case.prover_address.clone(), + &QueryMsg::GetProof { + multisig_session_id, + }, + ) + } + + #[test] + fn test_instantiation() { + let instantiator = "instantiator"; + let admin = "admin"; + let gateway_address = "gateway_address"; + let multisig_address = "multisig_address"; + let service_registry_address = "service_registry_address"; + let destination_chain_id = Uint256::one(); + let signing_threshold = ( + test_data::threshold().numerator(), + test_data::threshold().denominator(), + ) + .try_into() + .unwrap(); + let service_name = "service_name"; - let batch = CommandBatch::new(vec![], config.destination_chain_id, Some(new_worker_set))?; + let mut deps = mock_dependencies(); + let info = mock_info(&instantiator, &[]); + let env = mock_env(); - let start_sig_msg = multisig::msg::ExecuteMsg::StartSigningSession { - key_id: "static".to_string(), // TODO remove the key_id - msg: batch.msg_to_sign(), + let msg = InstantiateMsg { + admin_address: admin.to_string(), + gateway_address: gateway_address.to_string(), + multisig_address: multisig_address.to_string(), + service_registry_address: service_registry_address.to_string(), + destination_chain_id, + signing_threshold, + service_name: service_name.to_string(), + chain_name: "Ethereum".to_string(), + worker_set_diff_threshold: 0, }; - // TODO handle the reply - Ok(Response::new().add_message(wasm_execute(config.multisig, &start_sig_msg, vec![])?)) + let res = instantiate(deps.as_mut(), env, info, msg); + + assert!(res.is_ok()); + let res = res.unwrap(); + + assert_eq!(res.messages.len(), 0); + + let config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!(config.admin, admin); + assert_eq!(config.gateway, gateway_address); + assert_eq!(config.multisig, multisig_address); + assert_eq!(config.service_registry, service_registry_address); + assert_eq!(config.destination_chain_id, destination_chain_id); + assert_eq!( + config.signing_threshold, + signing_threshold.try_into().unwrap() + ); + assert_eq!(config.service_name, service_name); } - pub fn should_update_worker_set( - new_workers: &WorkerSet, - cur_workers: &WorkerSet, - max_diff: usize, - ) -> bool { - let count_new = |a: &WorkerSet, b: &WorkerSet| { - a.signers - .iter() - .filter(|a_signer| { - !b.signers.iter().any(|b_signer| { - a_signer.address == b_signer.address && a_signer.pub_key == b_signer.pub_key - }) - }) - .count() - }; - count_new(new_workers, cur_workers) + count_new(cur_workers, new_workers) > max_diff + #[test] + fn test_key_gen() { + let mut test_case = setup_test_case(); + let res = execute_key_gen(&mut test_case); + + assert!(res.is_ok()); } -} -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetProof { proof_id } => to_binary(&query::get_proof(proof_id)?), + #[test] + fn test_construct_proof() { + let mut test_case = setup_test_case(); + execute_key_gen(&mut test_case).unwrap(); + + let res = execute_construct_proof(&mut test_case, None).unwrap(); + + let event = res + .events + .iter() + .find(|event| event.ty == "wasm-proof_under_construction"); + + assert!(event.is_some()); } -} -pub mod query { - use super::*; + #[test] + fn test_query_proof() { + let mut test_case = setup_test_case(); + execute_key_gen(&mut test_case).unwrap(); + execute_construct_proof(&mut test_case, None).unwrap(); + + let res = query_get_proof(&mut test_case, None).unwrap(); + + assert_eq!(res.multisig_session_id, MULTISIG_SESSION_ID); + assert_eq!(res.message_ids.len(), 2); + match res.status { + ProofStatus::Completed { execute_data } => { + let tokens = + ethabi::decode(&[ParamType::Bytes], &execute_data.as_slice()[4..]).unwrap(); - pub fn get_proof(_proof_id: String) -> StdResult { - todo!() + let input = match tokens[0].clone() { + Token::Bytes(input) => input, + _ => panic!("Invalid proof"), + }; + + let tokens = + ethabi::decode(&[ParamType::Bytes, ParamType::Bytes], input.as_slice()) + .unwrap(); + + assert_eq!( + tokens, + vec![ + Token::Bytes(res.data.encode().to_vec()), + Token::Bytes(test_data::encoded_proof().to_vec()) + ] + ); + } + _ => panic!("Expected proof status to be completed"), // multisig mock will always return completed multisig + } } } diff --git a/contracts/multisig-prover/src/encoding/evm.rs b/contracts/multisig-prover/src/encoding/evm.rs index 3ba428dd4..9a3efeae9 100644 --- a/contracts/multisig-prover/src/encoding/evm.rs +++ b/contracts/multisig-prover/src/encoding/evm.rs @@ -13,7 +13,7 @@ use multisig::{msg::Signer, types::Signature}; use crate::{ error::ContractError, state::WorkerSet, - types::{Command, CommandBatch, CommandType, Operator}, + types::{BatchID, Command, CommandBatch, CommandType, Operator}, }; const GATEWAY_EXECUTE_FUNCTION_NAME: &str = "execute"; @@ -108,7 +108,7 @@ impl CommandBatch { commands, }; - let id = batch_id(&message_ids, new_worker_set); + let id = BatchID::new(&message_ids, new_worker_set); Ok(Self { id, @@ -255,7 +255,7 @@ impl Data { fn evm_address(pub_key: &[u8]) -> Result { let pub_key = - PublicKey::from_sec1_bytes(pub_key).map_err(|e| ContractError::InvalidMessage { + PublicKey::from_sec1_bytes(pub_key).map_err(|e| ContractError::InvalidPublicKey { reason: e.to_string(), })?; let pub_key = pub_key.to_encoded_point(false); @@ -297,16 +297,6 @@ fn command_params( .into() } -fn batch_id(message_ids: &[String], new_worker_set: Option) -> HexBinary { - let mut message_ids = message_ids.to_vec(); - message_ids.sort(); - if let Some(new_worker_set) = new_worker_set { - message_ids.push(new_worker_set.hash().to_string()) - } - - Keccak256::digest(message_ids.join(",")).as_slice().into() -} - #[cfg(test)] mod test { use ethabi::ParamType; @@ -577,7 +567,7 @@ mod test { }; let tokens = - ethabi::decode(&[ParamType::Bytes, ParamType::Bytes], &input.as_slice()).unwrap(); + ethabi::decode(&[ParamType::Bytes, ParamType::Bytes], input.as_slice()).unwrap(); assert_eq!( execute_data.as_slice()[0..4], @@ -622,7 +612,7 @@ mod test { let quorum = test_data::quorum(); let batch = CommandBatch { - id: HexBinary::from_hex("00").unwrap(), + id: HexBinary::from_hex("00").unwrap().into(), message_ids: vec![], data: decode_data(&test_data::encoded_data()), }; @@ -660,10 +650,10 @@ mod test { let mut message_ids: Vec = messages.iter().map(|msg| msg.id.clone()).collect(); message_ids.sort(); - let res = batch_id(&message_ids, None); + let res = BatchID::new(&message_ids, None); message_ids.reverse(); - let res2 = batch_id(&message_ids, None); + let res2 = BatchID::new(&message_ids, None); assert_eq!(res, res2); } @@ -681,7 +671,7 @@ mod test { #[test] fn test_msg_to_sign() { let batch = CommandBatch { - id: HexBinary::from_hex("00").unwrap(), + id: HexBinary::from_hex("00").unwrap().into(), message_ids: vec![], data: decode_data(&test_data::encoded_data()), }; diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index f91ef6f34..a71ed759b 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -7,6 +7,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("caller is not authorized")] + Unauthorized, + #[error("message is invalid: {reason}")] InvalidMessage { reason: String }, @@ -21,4 +24,16 @@ pub enum ContractError { #[error("worker set has not changed sufficiently since last update")] WorkerSetUnchanged, + + #[error("public key is invalid: {reason}")] + InvalidPublicKey { reason: String }, + + #[error("chain name is invalid")] + InvalidChainName, + + #[error("invalid participants: {reason}")] + InvalidParticipants { reason: String }, + + #[error("invalid contract reply: {reason}")] + InvalidContractReply { reason: String }, } diff --git a/contracts/multisig-prover/src/events.rs b/contracts/multisig-prover/src/events.rs index 9d799193d..68ab54aeb 100644 --- a/contracts/multisig-prover/src/events.rs +++ b/contracts/multisig-prover/src/events.rs @@ -1,16 +1,55 @@ -use cosmwasm_std::HexBinary; +use std::collections::HashMap; + +use axelar_wasm_std::Snapshot; +use cosmwasm_std::{HexBinary, Uint64}; +use serde_json::to_string; + +use crate::types::BatchID; pub enum Event { - ProofUnderConstruction { proof_id: HexBinary }, + ProofUnderConstruction { + command_batch_id: BatchID, + multisig_session_id: Uint64, + }, + SnapshotRotated { + key_id: String, + snapshot: Snapshot, + pub_keys: HashMap, + }, } impl From for cosmwasm_std::Event { fn from(other: Event) -> Self { match other { - Event::ProofUnderConstruction { proof_id } => { - cosmwasm_std::Event::new("proof_under_construction") - .add_attribute("proof_id", proof_id.to_hex()) - } + Event::ProofUnderConstruction { + command_batch_id, + multisig_session_id, + } => cosmwasm_std::Event::new("proof_under_construction") + .add_attribute( + "command_batch_id", + to_string(&command_batch_id) + .expect("violated invariant: command_batch_id is not serializable"), + ) + .add_attribute( + "multisig_session_id", + to_string(&multisig_session_id) + .expect("violated invariant: multisig_session_id is not serializable"), + ), + Event::SnapshotRotated { + key_id, + snapshot, + pub_keys, + } => cosmwasm_std::Event::new("snapshot_rotated") + .add_attribute("key_id", key_id) + .add_attribute( + "snapshot", + to_string(&snapshot).expect("violated invariant: snapshot is not serializable"), + ) + .add_attribute( + "pub_keys", + to_string(&pub_keys) + .expect("violated invariant: pub_keys are not serializable"), + ), } } } diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/execute.rs new file mode 100644 index 000000000..fe2115c2b --- /dev/null +++ b/contracts/multisig-prover/src/execute.rs @@ -0,0 +1,180 @@ +use cosmwasm_std::{ + to_binary, wasm_execute, Addr, DepsMut, Env, QuerierWrapper, QueryRequest, Response, SubMsg, + WasmQuery, +}; +use multisig::types::PublicKey; + +use std::str::FromStr; + +use axelar_wasm_std::snapshot; +use connection_router::{msg::Message, types::ChainName}; +use service_registry::state::Worker; + +use crate::{ + contract::START_MULTISIG_REPLY_ID, + error::ContractError, + state::{ + WorkerSet, COMMANDS_BATCH, CONFIG, CURRENT_WORKER_SET, KEY_ID, NEXT_WORKER_SET, REPLY_BATCH, + }, + types::{BatchID, CommandBatch}, +}; + +pub fn construct_proof(deps: DepsMut, message_ids: Vec) -> Result { + let key_id = KEY_ID.load(deps.storage)?; + let config = CONFIG.load(deps.storage)?; + + let batch_id = BatchID::new(&message_ids, None); + + let messages = get_messages(deps.querier, message_ids, config.gateway, config.chain_name)?; + + let command_batch = match COMMANDS_BATCH.may_load(deps.storage, &batch_id)? { + Some(batch) => batch, + None => { + let batch = CommandBatch::new(messages, config.destination_chain_id, None)?; + + COMMANDS_BATCH.save(deps.storage, &batch.id, &batch)?; + + batch + } + }; + + // keep track of the batch id to use during submessage reply + REPLY_BATCH.save(deps.storage, &command_batch.id)?; + + let start_sig_msg = multisig::msg::ExecuteMsg::StartSigningSession { + key_id, + msg: command_batch.msg_to_sign(), + }; + + let wasm_msg = wasm_execute(config.multisig, &start_sig_msg, vec![])?; + + Ok(Response::new().add_submessage(SubMsg::reply_on_success(wasm_msg, START_MULTISIG_REPLY_ID))) +} + +fn get_messages( + querier: QuerierWrapper, + message_ids: Vec, + gateway: Addr, + chain_name: ChainName, +) -> Result, ContractError> { + let length = message_ids.len(); + + let query = gateway::msg::QueryMsg::GetMessages { message_ids }; + let messages: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: gateway.into(), + msg: to_binary(&query)?, + }))?; + + assert!( + messages.len() == length, + "violated invariant: returned gateway messages count mismatch" + ); + + if messages.iter().any(|msg| { + ChainName::from_str(&msg.destination_chain) + .expect("violated invariant: message with invalid chain found") + != chain_name + }) { + panic!("violated invariant: messages from different chain found"); + } + + Ok(messages) +} + +pub fn update_worker_set(deps: DepsMut, env: Env) -> Result { + let config = CONFIG.load(deps.storage)?; + + let active_workers_query = service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: config.service_name, + chain_name: config.chain_name.into(), + }; + let workers: Vec = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.service_registry.to_string(), + msg: to_binary(&active_workers_query)?, + }))?; + + let participants = workers + .clone() + .into_iter() + .map(service_registry::state::Worker::try_into) + .collect::, _>>()?; + + let snapshot = snapshot::Snapshot::new( + env.block.time.try_into()?, + env.block.height.try_into()?, + config.signing_threshold, + participants.try_into()?, + ); + let worker_addresses: Vec = workers.iter().map(|w| w.address.to_string()).collect(); + + let pub_keys_query = multisig::msg::QueryMsg::GetPublicKeys { + worker_addresses: worker_addresses.clone(), + key_type: multisig::types::KeyType::ECDSA, + }; + let pub_keys: Vec = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.multisig.to_string(), + msg: to_binary(&pub_keys_query)?, + }))?; + + let new_worker_set = WorkerSet::new(snapshot.clone(), pub_keys.clone(), env)?; + + let cur_worker_set = CURRENT_WORKER_SET.may_load(deps.storage)?; + + // if no worker set, just store it and return + if cur_worker_set.is_none() { + CURRENT_WORKER_SET.save(deps.storage, &new_worker_set)?; + KEY_ID.save(deps.storage, &new_worker_set.hash().to_hex())?; + let key_gen_msg = multisig::msg::ExecuteMsg::KeyGen { + key_id: new_worker_set.hash().to_hex(), + snapshot, + pub_keys: worker_addresses + .into_iter() + .zip(pub_keys.into_iter()) + .collect(), + }; + return Ok(Response::new().add_message(wasm_execute( + config.multisig, + &key_gen_msg, + vec![], + )?)); + } + + if !should_update_worker_set( + &new_worker_set, + &cur_worker_set.unwrap(), + config.worker_set_diff_threshold as usize, + ) { + return Err(ContractError::WorkerSetUnchanged); + } + + NEXT_WORKER_SET.save(deps.storage, &new_worker_set)?; + + let batch = CommandBatch::new(vec![], config.destination_chain_id, Some(new_worker_set))?; + + let start_sig_msg = multisig::msg::ExecuteMsg::StartSigningSession { + key_id: KEY_ID.load(deps.storage)?, + msg: batch.msg_to_sign(), + }; + + let wasm_msg = wasm_execute(config.multisig, &start_sig_msg, vec![])?; + + Ok(Response::new().add_submessage(SubMsg::reply_on_success(wasm_msg, START_MULTISIG_REPLY_ID))) +} + +pub fn should_update_worker_set( + new_workers: &WorkerSet, + cur_workers: &WorkerSet, + max_diff: usize, +) -> bool { + let count_new = |a: &WorkerSet, b: &WorkerSet| { + a.signers + .iter() + .filter(|a_signer| { + !b.signers.iter().any(|b_signer| { + a_signer.address == b_signer.address && a_signer.pub_key == b_signer.pub_key + }) + }) + .count() + }; + count_new(new_workers, cur_workers) + count_new(cur_workers, new_workers) > max_diff +} diff --git a/contracts/multisig-prover/src/lib.rs b/contracts/multisig-prover/src/lib.rs index f23e00140..80f99be28 100644 --- a/contracts/multisig-prover/src/lib.rs +++ b/contracts/multisig-prover/src/lib.rs @@ -2,7 +2,10 @@ pub mod contract; pub mod encoding; pub mod error; pub mod events; +mod execute; pub mod msg; +mod query; +mod reply; pub mod state; pub mod types; diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index 6051a50e4..69ae29309 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -1,8 +1,22 @@ +use axelar_wasm_std::Threshold; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::HexBinary; +use cosmwasm_std::{HexBinary, Uint256, Uint64}; use crate::encoding::Data; +#[cw_serde] +pub struct InstantiateMsg { + pub admin_address: String, + pub gateway_address: String, + pub multisig_address: String, + pub service_registry_address: String, + pub destination_chain_id: Uint256, + pub signing_threshold: Threshold, + pub service_name: String, + pub chain_name: String, + pub worker_set_diff_threshold: u32, +} + #[cw_serde] pub enum ExecuteMsg { // Start building a proof that includes specified messages @@ -15,7 +29,7 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { #[returns(GetProofResponse)] - GetProof { proof_id: String }, + GetProof { multisig_session_id: Uint64 }, } #[cw_serde] @@ -26,7 +40,7 @@ pub enum ProofStatus { #[cw_serde] pub struct GetProofResponse { - pub proof_id: HexBinary, + pub multisig_session_id: Uint64, pub message_ids: Vec, pub data: Data, pub status: ProofStatus, diff --git a/contracts/multisig-prover/src/query.rs b/contracts/multisig-prover/src/query.rs new file mode 100644 index 000000000..6384777d6 --- /dev/null +++ b/contracts/multisig-prover/src/query.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::{to_binary, Deps, QueryRequest, StdError, StdResult, Uint64, WasmQuery}; + +use multisig::{msg::Multisig, types::MultisigState}; + +use crate::{ + msg::{GetProofResponse, ProofStatus}, + state::{COMMANDS_BATCH, CONFIG, MULTISIG_SESSION_BATCH}, +}; + +pub fn get_proof(deps: Deps, multisig_session_id: Uint64) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let batch_id = MULTISIG_SESSION_BATCH.load(deps.storage, multisig_session_id.u64())?; + + let batch = COMMANDS_BATCH.load(deps.storage, &batch_id)?; + + let query_msg = multisig::msg::QueryMsg::GetMultisig { + session_id: multisig_session_id, + }; + + let multisig: Multisig = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.multisig.to_string(), + msg: to_binary(&query_msg)?, + }))?; + + let status = match multisig.state { + MultisigState::Pending => ProofStatus::Pending, + MultisigState::Completed => { + let execute_data = batch + .encode_execute_data(multisig.quorum, multisig.signers) + .map_err(|e| { + StdError::generic_err(format!("failed to encode execute data: {}", e)) + })?; + + ProofStatus::Completed { execute_data } + } + }; + + Ok(GetProofResponse { + multisig_session_id, + message_ids: batch.message_ids, + data: batch.data, + status, + }) +} diff --git a/contracts/multisig-prover/src/reply.rs b/contracts/multisig-prover/src/reply.rs new file mode 100644 index 000000000..86d98850f --- /dev/null +++ b/contracts/multisig-prover/src/reply.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{from_binary, DepsMut, Reply, Response, Uint64}; +use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; + +use crate::{ + error::ContractError, + events::Event, + state::{MULTISIG_SESSION_BATCH, REPLY_BATCH}, +}; + +pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { + match parse_reply_execute_data(reply) { + Ok(MsgExecuteContractResponse { data: Some(data) }) => { + let command_batch_id = REPLY_BATCH.load(deps.storage)?; + + let multisig_session_id: Uint64 = + from_binary(&data).map_err(|_| ContractError::InvalidContractReply { + reason: "invalid multisig session ID".to_string(), + })?; + + MULTISIG_SESSION_BATCH.save( + deps.storage, + multisig_session_id.u64(), + &command_batch_id, + )?; + + Ok(Response::new().add_event( + Event::ProofUnderConstruction { + command_batch_id, + multisig_session_id, + } + .into(), + )) + } + Ok(MsgExecuteContractResponse { data: None }) => Err(ContractError::InvalidContractReply { + reason: "no data".to_string(), + }), + Err(_) => { + unreachable!("violated invariant: replied failed submessage with ReplyOn::Success") + } + } +} diff --git a/contracts/multisig-prover/src/state.rs b/contracts/multisig-prover/src/state.rs index 5e402fd07..4848c0c57 100644 --- a/contracts/multisig-prover/src/state.rs +++ b/contracts/multisig-prover/src/state.rs @@ -2,11 +2,12 @@ use axelar_wasm_std::{Snapshot, Threshold}; use connection_router::types::ChainName; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Env, HexBinary, Uint256}; -use cw_storage_plus::Item; +use cw_storage_plus::{Item, Map}; use multisig::msg::Signer; use sha3::{Digest, Keccak256}; use crate::error::ContractError; +use crate::types::{BatchID, CommandBatch}; #[cw_serde] pub struct Config { @@ -21,8 +22,6 @@ pub struct Config { pub worker_set_diff_threshold: u32, } -pub const CONFIG: Item = Item::new("config"); - #[cw_serde] pub struct WorkerSet { pub signers: Vec, @@ -61,3 +60,10 @@ impl WorkerSet { } pub const CURRENT_WORKER_SET: Item = Item::new("current_worker_set"); pub const NEXT_WORKER_SET: Item = Item::new("next_worker_set"); + +pub const CONFIG: Item = Item::new("config"); +pub const KEY_ID: Item = Item::new("key_id"); +pub const COMMANDS_BATCH: Map<&BatchID, CommandBatch> = Map::new("command_batch"); +pub const MULTISIG_SESSION_BATCH: Map = Map::new("multisig_session_batch"); + +pub const REPLY_BATCH: Item = Item::new("reply_tracker"); diff --git a/contracts/multisig-prover/src/test/mocks/gateway.rs b/contracts/multisig-prover/src/test/mocks/gateway.rs new file mode 100644 index 000000000..016b2806d --- /dev/null +++ b/contracts/multisig-prover/src/test/mocks/gateway.rs @@ -0,0 +1,33 @@ +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, +}; +use gateway::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +use crate::test::test_data; + +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + Ok(Response::default()) +} + +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + Ok(Response::default()) +} + +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetMessages { message_ids: _ } => { + let res = test_data::messages(); + to_binary(&res) + } + } +} diff --git a/contracts/multisig-prover/src/test/mocks/mod.rs b/contracts/multisig-prover/src/test/mocks/mod.rs new file mode 100644 index 000000000..d7daeda5b --- /dev/null +++ b/contracts/multisig-prover/src/test/mocks/mod.rs @@ -0,0 +1,3 @@ +pub mod gateway; +pub mod multisig; +pub mod service_registry; diff --git a/contracts/multisig-prover/src/test/mocks/multisig.rs b/contracts/multisig-prover/src/test/mocks/multisig.rs new file mode 100644 index 000000000..25f9ce88d --- /dev/null +++ b/contracts/multisig-prover/src/test/mocks/multisig.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint64, +}; +use multisig::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + types::MultisigState, +}; + +use self::query::get_public_keys_query_success; + +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + Ok(Response::default()) +} + +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::StartSigningSession { key_id: _, msg: _ } => { + Ok(Response::new().set_data(to_binary(&Uint64::one())?)) + } + ExecuteMsg::SubmitSignature { + session_id: _, + signature: _, + } => unimplemented!(), + ExecuteMsg::KeyGen { + key_id: _, + snapshot: _, + pub_keys: _, + } => Ok(Response::default()), + ExecuteMsg::RegisterPublicKey { + public_key: _, + key_type: _, + } => Ok(Response::default()), + } +} + +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetMultisig { session_id: _ } => to_binary(&query::multisig_query_success()), + QueryMsg::GetKey { key_id: _ } => unimplemented!(), + QueryMsg::GetPublicKeys { + worker_addresses: _, + key_type: _, + } => to_binary(&get_public_keys_query_success()), + } +} + +mod query { + use multisig::{ + msg::{Multisig, Signer}, + types::{PublicKey, Signature}, + }; + + use crate::test::test_data; + + use super::*; + + pub fn multisig_query_success() -> Multisig { + let operators = test_data::operators(); + let quorum = test_data::quorum(); + + let signers = operators + .into_iter() + .map(|op| { + ( + Signer { + address: op.address, + weight: op.weight.into(), + pub_key: PublicKey::ECDSA(op.pub_key), + }, + op.signature.map(|sig| Signature::ECDSA(sig)), + ) + }) + .collect::)>>(); + + Multisig { + state: MultisigState::Completed, + quorum, + signers, + } + } + pub fn get_public_keys_query_success() -> Vec { + test_data::operators() + .into_iter() + .map(|op| PublicKey::ECDSA(op.pub_key)) + .collect::>() + } +} diff --git a/contracts/multisig-prover/src/test/mocks/service_registry.rs b/contracts/multisig-prover/src/test/mocks/service_registry.rs new file mode 100644 index 000000000..f9f0195ce --- /dev/null +++ b/contracts/multisig-prover/src/test/mocks/service_registry.rs @@ -0,0 +1,50 @@ +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, +}; +use service_registry::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + state::{AuthorizationState, BondingState, Worker}, +}; + +use crate::test::test_data; + +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + Ok(Response::default()) +} + +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + Ok(Response::default()) +} + +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetActiveWorkers { + service_name, + chain_name: _, + } => { + let workers = test_data::operators() + .into_iter() + .map(|op| Worker { + address: op.address, + bonding_state: BondingState::Bonded { + amount: op.weight.try_into().unwrap(), + }, + authorization_state: AuthorizationState::Authorized, + service_name: service_name.clone(), + }) + .collect::>(); + + to_binary(&workers) + } + } +} diff --git a/contracts/multisig-prover/src/test/mod.rs b/contracts/multisig-prover/src/test/mod.rs index 363a59ff8..451f6c4cd 100644 --- a/contracts/multisig-prover/src/test/mod.rs +++ b/contracts/multisig-prover/src/test/mod.rs @@ -1 +1,3 @@ +pub mod mocks; +pub mod multicontract; pub mod test_data; diff --git a/contracts/multisig-prover/src/test/multicontract.rs b/contracts/multisig-prover/src/test/multicontract.rs new file mode 100644 index 000000000..3c9c5c657 --- /dev/null +++ b/contracts/multisig-prover/src/test/multicontract.rs @@ -0,0 +1,169 @@ +use cosmwasm_std::{Addr, Coin, Empty, Uint128}; +use cw_multi_test::{next_block, App, AppBuilder, Contract, ContractWrapper, Executor}; + +use super::{mocks, test_data}; + +pub const INSTANTIATOR: &str = "instantiator"; +pub const RELAYER: &str = "relayer"; + +pub struct TestCaseConfig { + pub app: App, + pub admin: Addr, + pub prover_address: Addr, +} + +pub fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(RELAYER), + vec![Coin { + denom: "uaxl".to_string(), + amount: Uint128::from(100u8), + }], + ) + .unwrap(); + }) +} + +fn contract_multisig() -> Box> { + let contract = ContractWrapper::new( + mocks::multisig::execute, + mocks::multisig::instantiate, + mocks::multisig::query, + ); + Box::new(contract) +} + +fn instantiate_mock_multisig(app: &mut App) -> Addr { + let code_id = app.store_code(contract_multisig()); + let msg = multisig::msg::InstantiateMsg {}; + + app.instantiate_contract( + code_id, + Addr::unchecked(INSTANTIATOR), + &msg, + &[], + "mock-multisig", + None, + ) + .unwrap() +} + +fn contract_gateway() -> Box> { + let contract = ContractWrapper::new( + mocks::gateway::execute, + mocks::gateway::instantiate, + mocks::gateway::query, + ); + Box::new(contract) +} + +fn instantiate_mock_gateway(app: &mut App) -> Addr { + let code_id = app.store_code(contract_gateway()); + let msg = gateway::msg::InstantiateMsg { + verifier_address: "verifier".to_string(), + router_address: "router".to_string(), + }; + + app.instantiate_contract( + code_id, + Addr::unchecked(INSTANTIATOR), + &msg, + &[], + "mock-gateway", + None, + ) + .unwrap() +} + +fn contract_service_registry() -> Box> { + let contract = ContractWrapper::new( + mocks::service_registry::execute, + mocks::service_registry::instantiate, + mocks::service_registry::query, + ); + Box::new(contract) +} + +fn instantiate_mock_service_registry(app: &mut App) -> Addr { + let code_id = app.store_code(contract_service_registry()); + let msg = service_registry::msg::InstantiateMsg { + governance_account: "governance".to_string(), + }; + + app.instantiate_contract( + code_id, + Addr::unchecked(INSTANTIATOR), + &msg, + &[], + "mock-service-registry", + None, + ) + .unwrap() +} + +fn contract_prover() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply(crate::contract::reply); + Box::new(contract) +} + +fn instantiate_prover( + app: &mut App, + gateway_address: String, + multisig_address: String, + service_registry_address: String, +) -> Addr { + let code_id = app.store_code(contract_prover()); + let msg = crate::msg::InstantiateMsg { + admin_address: INSTANTIATOR.to_string(), + gateway_address, + multisig_address, + service_registry_address, + destination_chain_id: test_data::destination_chain_id(), + signing_threshold: test_data::threshold(), + service_name: "service-name".to_string(), + chain_name: "Ethereum".to_string(), + worker_set_diff_threshold: 0, + }; + + app.instantiate_contract( + code_id, + Addr::unchecked(INSTANTIATOR), + &msg, + &[], + "prover", + None, + ) + .unwrap() +} + +pub fn setup_test_case() -> TestCaseConfig { + let mut app = mock_app(); + + let gateway_address = instantiate_mock_gateway(&mut app); + let multisig_address = instantiate_mock_multisig(&mut app); + let service_registry_address = instantiate_mock_service_registry(&mut app); + + let prover_address = instantiate_prover( + &mut app, + gateway_address.to_string(), + multisig_address.to_string(), + service_registry_address.to_string(), + ); + + app.update_block(next_block); + + TestCaseConfig { + app, + admin: Addr::unchecked(INSTANTIATOR), + prover_address, + } +} diff --git a/contracts/multisig-prover/src/test/test_data.rs b/contracts/multisig-prover/src/test/test_data.rs index 82b47493a..ee7670b1a 100644 --- a/contracts/multisig-prover/src/test/test_data.rs +++ b/contracts/multisig-prover/src/test/test_data.rs @@ -1,6 +1,7 @@ // Test data taken from production axelarscan batch // https://axelarscan.io/batch/ethereum/0304b99223f238f417cd015b724d32081a19cee49a41a839b73cd16ccaa538ab +use axelar_wasm_std::{nonempty, Threshold}; use connection_router::msg::Message; use cosmwasm_std::{Addr, HexBinary, Uint256, Uint64}; use multisig::{ @@ -173,6 +174,12 @@ pub fn execute_data() -> HexBinary { HexBinary::from_hex("09c5eabe00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000002cdf61b5aa2024f5a27383b0785fc393c566eef69569cf5abec945794b097bb734ddf46ac2855e6614da7c654d224a58ad9eb9f567c45432c5120aa83a772a1e50000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000013617070726f7665436f6e747261637443616c6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013617070726f7665436f6e747261637443616c6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000005a8aa0ed1e1bc598c23b415f67cd774b530546cdf0e679e57348329e51e4337b7839882c29f21a3095a718c239f147b143f9ff8c8a0024fa264d538986271bdf8d2901c443321faa33440b9f28e38ea28e6141f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007506f6c79676f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783636343233613162343565313445614238423133323636354665624337456338364266634246343400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000005a8aa0ed1e1bc598c23b415f67cd774b530546cd8f619df9786ea29e466d37c846576a49080089909bf228c19458739606341a5e7a7263a63ac449b4c6ce2a93accfae9ae49c1d96e3fa9c19cc417130bcfda2200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007506f6c79676f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783636343233613162343565313445614238423133323636354665624337456338364266634246343400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000001e7070000000000000000000000000000000000000000000000000000000000001240000000000000000000000000000000000000000000000000000000000000004600000000000000000000000001212e8f3996651d6978147e76aa1f36c34b65560000000000000000000000000288250c792266bc32bff76ef6f1d004f4f8df0c0000000000000000000000000710ccd75da5092233e38eb551002fd430df9cbe000000000000000000000000088e9daba07d726c5799cdead398e126b6fa1ceb0000000000000000000000000bc0739613e797e7657a47eff38c5f65f5f09e020000000000000000000000000d82cb5aef0e962cef6b6c691f8629c239c794ba00000000000000000000000011725cfbb03c375a6e5001a15ecc29ff8d9189ab00000000000000000000000014c1dc01abb5c98f0331bd148d35c0833028c7610000000000000000000000001813a871150d5c19e98f1f0d9d64fb6ce3aba3f80000000000000000000000001c903d3934ea778a00dabaaf1ddfee71e4bb7c84000000000000000000000000244d87c20ba95419cfd0346b7add9ab129d841d100000000000000000000000024e8436b9529002e8ae85a6373fadeb2d6a904b60000000000000000000000002530cc75eb84f07f026c7f7980e5b291a633db790000000000000000000000002b2c588952a7c2dce5407bed5cd7e2b6a895e1010000000000000000000000002cc804902b486be9e82f5017e94ed58f5f93669c00000000000000000000000032d10eb789c05f27533d585fbd5204bcf4de335a00000000000000000000000038942584eaf389c65c33f1a3865c969d7222175b0000000000000000000000003c42dd06a0ae5d2efaf31f6365307aa2a93f3a79000000000000000000000000420eec77dfba11877204db375009cbdaee4821e6000000000000000000000000434cefe1e08f77f046ae867da8fbe4d8f37c869a000000000000000000000000435783cea6845b16e65e2a297a14eca5edbd3b1f0000000000000000000000004438edac9c5c8e50eef87b7164989be7034764e100000000000000000000000044c6bf775b39b999ae1016fc3687f5494d6175f80000000000000000000000004541647125cb73559793b5601ad23e95b400e36200000000000000000000000045a6340ac876e1900f9f48dfdb150371c5a3c509000000000000000000000000463242d252b525247ac8635880211a5a448449db0000000000000000000000004a23083cf3ba4aac49dfba633e3a18bf0d86e66c0000000000000000000000004c2fbbb6ca701826b25a04649d5e847a4bb5912f000000000000000000000000506a2e5f76b6588cafb2147366f4b682770be1ec00000000000000000000000051d62fbc02011df3da608d0d8290790cbff598080000000000000000000000005fc6c6750dfa024329c6c2ba3ccd36a446610e760000000000000000000000006870aff7b9b6f70f21601003f8e6be2b18214e870000000000000000000000007194cc071c8d4ef668b2e74b62ddfc47373521f60000000000000000000000007a0fffaa24356f564802d1fc99304d03080b506b000000000000000000000000827c05a0378e1edf9526d2477e452fbb20f52b0a000000000000000000000000917d59041fae0fe4ca38bdb9fbd1a91f85f8842200000000000000000000000097ba16aa228fdee5bffbd7b3010869185deb1e6a000000000000000000000000a393da0f437a72732415969f6c7c6cd837f6eb0a000000000000000000000000a65ecf78762279741284b1da4c1708f13bb5c156000000000000000000000000a7792df62b1f21b7e2686ae218fb50da4202c4f2000000000000000000000000a8522550ee4071518d4fc4191e3884cca876c7c5000000000000000000000000aaa9487c10a6238c49c56d0eaaaefaabd0a7520d000000000000000000000000ad65f42b44c4a002f6f321965c126674f5467010000000000000000000000000af0e301a625f5ac6de4471513b42a106bbd765e4000000000000000000000000b1547868e5fe7d4b55f5a6150d1a32ebf12335ab000000000000000000000000b305c9dd6d8bb87e735ac889a1c7664ac8871417000000000000000000000000b4d0a603d133981e274f12e581cd801499d24148000000000000000000000000b68d7618fc56bdc5db360a491fb1c20743111b0c000000000000000000000000b998fe85433f906b40d7d5afcd6cf0fdc0afda55000000000000000000000000bc387697e78e6c6c12ae1bfd8b925c262afc5cf4000000000000000000000000c0507cff65788f80d361a4adddd843ac1abe5e5f000000000000000000000000c21c4a3f1688b1fdbb0828e7b23178b153049e7c000000000000000000000000c72f0caee69b6c22ac4c8907e9a28f2b100f78d6000000000000000000000000c82476e37d57bc759a9513c5009bbb30e48a0840000000000000000000000000c9f728aee5f8055249e47fea4fd6cbfd4265d1d1000000000000000000000000d40cbb664cf2d8dabfa06e28b876b839e7c28a49000000000000000000000000d4ce03464481c9e82adbe53dd496577c9a42909f000000000000000000000000d6af3f6269ca4c2f48eb49c47e41ecf6d9a1d51a000000000000000000000000d70846769b64dc14982468f1fd84f6002871a84a000000000000000000000000d9163bb47cfc354f3a6e99e64dd5cd612c1d8546000000000000000000000000d977f3f175a51df2fab3449977074e3c291e357d000000000000000000000000ddd218807e10116323ca9616199056745d4b1ca7000000000000000000000000e004b03ec50dec54e350cbf262e380a37a119760000000000000000000000000e264eba02966197f508240d0c735b3353d039935000000000000000000000000e66292178b09f6a5c8e7c4da17dfdd8f45ed6458000000000000000000000000e776c20f6d3577e14e92e9214b95c480e2bb8820000000000000000000000000eeb623c93d6d6cf439a123a73e3a656e8f398bf5000000000000000000000000f4c68a67011f2474a6146a33fb2c014921803546000000000000000000000000f95e1f5e2499626423b625fc6fef0da0069e40f3000000000000000000000000fbbe6332d107f1d573960bd24cded403441579df000000000000000000000000000000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000010610000000000000000000000000000000000000000000000000000000000000a87000000000000000000000000000000000000000000000000000000000000120f00000000000000000000000000000000000000000000000000000000000007590000000000000000000000000000000000000000000000000000000000000efd0000000000000000000000000000000000000000000000000000000000000ffb0000000000000000000000000000000000000000000000000000000000000a870000000000000000000000000000000000000000000000000000000000000d650000000000000000000000000000000000000000000000000000000000000d7e0000000000000000000000000000000000000000000000000000000000000a83000000000000000000000000000000000000000000000000000000000000061c00000000000000000000000000000000000000000000000000000000000010870000000000000000000000000000000000000000000000000000000000000c5a00000000000000000000000000000000000000000000000000000000000005300000000000000000000000000000000000000000000000000000000000000b1b0000000000000000000000000000000000000000000000000000000000000f380000000000000000000000000000000000000000000000000000000000000b390000000000000000000000000000000000000000000000000000000000000c8b00000000000000000000000000000000000000000000000000000000000006cb00000000000000000000000000000000000000000000000000000000000007c60000000000000000000000000000000000000000000000000000000000000e280000000000000000000000000000000000000000000000000000000000000cf70000000000000000000000000000000000000000000000000000000000000ee70000000000000000000000000000000000000000000000000000000000000eae0000000000000000000000000000000000000000000000000000000000000b3b000000000000000000000000000000000000000000000000000000000000074f00000000000000000000000000000000000000000000000000000000000007020000000000000000000000000000000000000000000000000000000000000d1900000000000000000000000000000000000000000000000000000000000007540000000000000000000000000000000000000000000000000000000000000b480000000000000000000000000000000000000000000000000000000000000cf30000000000000000000000000000000000000000000000000000000000000a2b00000000000000000000000000000000000000000000000000000000000007fb0000000000000000000000000000000000000000000000000000000000000b6b00000000000000000000000000000000000000000000000000000000000006d90000000000000000000000000000000000000000000000000000000000000cc800000000000000000000000000000000000000000000000000000000000010450000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000dd000000000000000000000000000000000000000000000000000000000000014be0000000000000000000000000000000000000000000000000000000000000757000000000000000000000000000000000000000000000000000000000000171a0000000000000000000000000000000000000000000000000000000000000bb2000000000000000000000000000000000000000000000000000000000000093400000000000000000000000000000000000000000000000000000000000012c70000000000000000000000000000000000000000000000000000000000000c900000000000000000000000000000000000000000000000000000000000000866000000000000000000000000000000000000000000000000000000000000075a00000000000000000000000000000000000000000000000000000000000007690000000000000000000000000000000000000000000000000000000000000e050000000000000000000000000000000000000000000000000000000000000a3c0000000000000000000000000000000000000000000000000000000000000ccb00000000000000000000000000000000000000000000000000000000000005ab0000000000000000000000000000000000000000000000000000000000000e6b00000000000000000000000000000000000000000000000000000000000005ae00000000000000000000000000000000000000000000000000000000000007d200000000000000000000000000000000000000000000000000000000000006150000000000000000000000000000000000000000000000000000000000000ba50000000000000000000000000000000000000000000000000000000000000fe700000000000000000000000000000000000000000000000000000000000006100000000000000000000000000000000000000000000000000000000000000cbc0000000000000000000000000000000000000000000000000000000000000e69000000000000000000000000000000000000000000000000000000000000084900000000000000000000000000000000000000000000000000000000000008f90000000000000000000000000000000000000000000000000000000000000cce00000000000000000000000000000000000000000000000000000000000008c40000000000000000000000000000000000000000000000000000000000001acd00000000000000000000000000000000000000000000000000000000000007750000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000071900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006800000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000880000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000d800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000e800000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010800000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001280000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000013800000000000000000000000000000000000000000000000000000000000000041283786d844a7c4d1d424837074d0c8ec71becdcba4dd42b5307cb543a0e2c8b81c10ad541defd5ce84d2a608fc454827d0b65b4865c8192a2ea1736a5c4b72021b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f14c1f4338985bd3602f4abf12b3aad290251ffb438343720f2fe7569bfde48f68d89e8ccbcfdd9100fb6ccd4a678f1d2b2751bb32e32dd3b9712c3441376dd51b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412248fa2ac00b2404200d684ff050d6756f67ff6a73a4b00baf1034109b4fbdb80006b2cfd2f373aba18520d16edf32c237df52aeeef46312fef31705035c2aaa1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004112794cd061d679706d162ca713c382de5fbd564128d1a230d25710fd4af542c05f63dac91bd85ae74aa1ca0f407cf39160ee93d47c45a33c6ab2800dad9d26d01b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004138004c34c0d6f9b844addaeabff71d3cfd655668883095fba4d9688bf29afbf70a52388091afd092669727c5e77437cd3654c790abd0042728b192d8d6d243ed1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004160fb2e4bfd01c106eb4dfefe69a0d8aab10cb99f4bed5639be541764ecedd7f6494f9627213846da6aaeef0ca112d45e8510daf52afeb413f4961c3226da43dd1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000411bf34a3e90bec100824e58edfafa8ef5abb4b5a932547321a61c5723351ae260091cb73cabfc292cb1ee186f9c59fbcdbbf857fb0d3ce4f84b35f3acae6fc60e1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041bb733537c9324af4c6fe23403bff5baf0a6896146ae0ae0e4c129669e54fd7341f75fabeebf48595b8498a6e27f9d307d7053da8d618252b2227139ef187a97e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419406f1091221fd575bcccede6d8d3dd28f505ece86c94850c938635e4b35eb80749ad9da091e60647413d4e3a51ba07dacb0848ca87e49cfb6295e7f7f43b1f11b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041461b53ca570b01c7b33886c81b4efb715c13f2fdde2c5e1f2cbd450df100190c1392f88bc7d2a2650ee92ccfd9534a10d62265606788ed899b89e643eba68ad51b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d6313bbb46bcd5ebbe8d9dde0e9abb60ca65d692ca22f1c63e17680ea223909f038c9907de7e9a25c2ea9985514e02a9f40029ead9503fb1543625c01136d0ec1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a58c9543b9df54578ec45838948e19afb1c6e4c86b34d9899b10b44e619ea74e19b457611e41a047030ed233af437d7ecff84de97cb6b3c13d73d22874e035111c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000416cda7d16055fcadeac7851480a497a59f6bd317dfaacbb14cd9f98b4df8f9a0c63aaecefccfd456d65e0fbc67b9eabdd9ce8718e9361deb2523131fba21de5ce1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000418335301f0243bd8bc0425f7141c429f2174b14c665f779d6d54fff58a5cd5d7c447df479aad9fca282593a1566c31512401cdf35bc9a29b3d8aed92f2013b3d31c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a112231719403227b297139cc6beef82a4e034663bfe48cf732687860b16227a51e4bd6be96fceeecf8e77fe7cdd4f5567d71aed5388484d1f2ba355298c954e1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041aa7f54f111135bf0aac5c40afc1c767c3aa3935193ed0aef1da06526db4c270b46ed87baadd2bfdec2e25cbd84f5a4d46de22de2bdc04ab8facb010042ffdcde1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041c520f1c0440dc2a63349577d4238bb5b840e7e7f6a7ecb990d98587f42b629994a7663de4deeffbbe0e4595834be3a467927e7f8046b3f171eef88ee9578ced91b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419b7b3013b210e79642f024892ceab71efe7691f9dc7f7eb8991f9e45d4a76fa301dba76b2bf72a5e98affdc3140437fa173733f08ba6339194aed51c691f05cd1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415734205ff9faa3af7a2e0d677c1f7b01e20d78b952fa8159e1a4963f4139770618085ee3697daa89700a6aa3e762af42065e358649ed1e06e1f573d3e8aae3fd1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004177119dce999a13dbd7fd8d468086a8dadd24873b70845e90a53bb549ce1a7664748153b2fe56bed58033a7ce5bd44e7eb07d2ec9c8eeb8f4a1323806e2a2b2681b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041da752ac17841fde9a4f61723dae15dcbdcd6c87f2d3ff7d80c7ad6c6cdb68f7a5f2b9d9d6be9a3f818f2589b5278ac1d06426b2d6692946fa896cbff1dd88c7d1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004114ffabdb0457a256917c155ea3ecdb7dc1f576b9a85e0231b6ed548557a660f31981c58e9f44ee2121a845d6573805bf9e82028f30c586cd9cb81d48d45f95961c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000417cb8ea927e1513ce84b2ae94346f8accfcab23cada57e63c8e832e0216c4b8191a40de11aacbdd78d85ffe895c93e2070dff986ab8f7de1606d4e6125e0bbade1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041aaf292ef9ba45e6c2ab987dfdfc4921eaf6b7d3c4fb5f2729a28d2902a395b7a1638aa1c52dae753a87775f965a177f2c307da6a88d2e0f737428e7062ba6c361b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000416f1e76384f12d6ca121a81259e254d40707fa00b70efcd7bc1dd8780edb9b4e77c9363ae5f3c9f19ad158fca8af6b3dd311ddc8ec755a7963f4f45f4baaada851b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410cf91e04afb41f0e2162fde5448f02d95e85070b2705736f6e042bd8e39dfaea3191da068ddc4f31482d835f6411094542ba4bf183be313c5fb4943daea221861b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f5dfa93f94f342bf980964824f5c6d2c208f31e2e20a38277aa90fa3810dc4e665567b9b02e6c4a7941cad378f97cb8574f1cad2da3faf6b9db46ee1919724d01b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414b428eff9ce83ffd803c61d98b7ccd449f23927e2fde4360d3dec71763f3e5ea1f586256112b1813abc209061176a516bc097d58350d978b08e140bf96ee72191c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419fafcf373377f14b64e7c5f137e10ace9963c85c8da1f8a9de58f863d9db6c900dfbeed717f67564a8fb0809e7889a2038ce410da986ebba8d9a44bbcdfc4ef81c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000417fd7a96f9c9641b4b41ea028b27b8013ff1d8cb0b42fbfc9206321c41836c61279974056e258f17d5de0757a388f5df233db60a8b23b901d52c71e7f415321641c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041c4df60c3cc3e0fd97d1c92246897868430d6b0c4c5e1e2042503c589c4119905704bf4c63b48f5789f790fcb0a754b090f9e6c8e99e1306b7b48bfad5ae6ecaf1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000418077f1a99aa203668c298fc6d0b64db5f499c889b4a5e07ddae41d6cf096a76e6287d1803daeec404d58c920d82d7ae3888becb4887155dcb4b11090fab537351c00000000000000000000000000000000000000000000000000000000000000").unwrap() } +pub fn threshold() -> Threshold { + let numerator: nonempty::Uint64 = Uint64::from(3u8).try_into().unwrap(); + let denominator: nonempty::Uint64 = Uint64::from(5u8).try_into().unwrap(); + (numerator, denominator).try_into().unwrap() +} + #[derive(Debug)] pub struct TestOperator { pub address: Addr, diff --git a/contracts/multisig-prover/src/types.rs b/contracts/multisig-prover/src/types.rs index eb6920de0..7043e6a3c 100644 --- a/contracts/multisig-prover/src/types.rs +++ b/contracts/multisig-prover/src/types.rs @@ -1,10 +1,13 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, Uint256}; +use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; +use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use multisig::types::ECDSASignature; +use sha3::{Digest, Keccak256}; use crate::encoding::Data; +use crate::state::WorkerSet; #[cw_serde] pub enum CommandType { @@ -28,9 +31,55 @@ pub struct Command { pub params: HexBinary, } +#[cw_serde] +pub struct BatchID(HexBinary); + +impl From for BatchID { + fn from(id: HexBinary) -> Self { + Self(id) + } +} + +impl From<&[u8]> for BatchID { + fn from(id: &[u8]) -> Self { + Self(id.into()) + } +} + +impl<'a> PrimaryKey<'a> for BatchID { + type Prefix = (); + type SubPrefix = (); + type Suffix = BatchID; + type SuperSuffix = BatchID; + + fn key(&self) -> Vec { + vec![Key::Ref(self.0.as_slice())] + } +} + +impl KeyDeserialize for BatchID { + type Output = BatchID; + + fn from_vec(value: Vec) -> StdResult { + Ok(from_binary(&value.into()).expect("violated invariant: BatchID is not deserializable")) + } +} + +impl BatchID { + pub fn new(message_ids: &[String], new_worker_set: Option) -> BatchID { + let mut message_ids = message_ids.to_vec(); + message_ids.sort(); + if let Some(new_worker_set) = new_worker_set { + message_ids.push(new_worker_set.hash().to_string()) + } + + Keccak256::digest(message_ids.join(",")).as_slice().into() + } +} + #[cw_serde] pub struct CommandBatch { - pub id: HexBinary, + pub id: BatchID, pub message_ids: Vec, pub data: Data, } diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index fbd6223a7..b8ba06e01 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -95,7 +95,9 @@ pub mod execute { msg, }; - Ok(Response::new().add_event(event.into())) + Ok(Response::new() + .set_data(to_binary(&session_id)?) + .add_event(event.into())) } pub fn submit_signature( @@ -147,8 +149,12 @@ pub mod execute { info: MessageInfo, key_id: String, snapshot: Snapshot, - pub_keys: HashMap, + pub_keys: HashMap, ) -> Result { + if snapshot.participants.len() != pub_keys.len() { + return Err(ContractError::PublicKeysMismatchParticipants {}); + } + for participant in snapshot.participants.keys() { if !pub_keys.contains_key(participant) { return Err(ContractError::MissingPublicKey { @@ -164,15 +170,7 @@ pub mod execute { let key = Key { id: key_id.clone(), snapshot, - pub_keys: pub_keys - .into_iter() - .map(|(k, v)| { - ( - k, - PublicKey::try_from(v).expect("failed to decode public key"), - ) - }) - .collect(), + pub_keys, }; KEYS.update(deps.storage, &key_id, |existing| match existing { @@ -295,7 +293,7 @@ mod tests { msg::Multisig, test::common::test_data, test::common::{build_snapshot, TestSigner}, - types::{ECDSASignature, KeyType, MultisigState, PublicKey, Signature}, + types::{ECDSAPublicKey, ECDSASignature, KeyType, MultisigState, PublicKey, Signature}, }; use super::*; @@ -329,10 +327,10 @@ mod tests { .map(|signer| { ( signer.address.clone().to_string(), - (KeyType::ECDSA, signer.pub_key.clone()), + PublicKey::ECDSA(ECDSAPublicKey::try_from(signer.pub_key.clone()).unwrap()), ) }) - .collect::>(); + .collect::>(); let subkey = "key".to_string(); let snapshot = build_snapshot(&signers); @@ -521,6 +519,7 @@ mod tests { assert_eq!(session.state, MultisigState::Pending); let res = res.unwrap(); + assert_eq!(res.data, Some(to_binary(&session.id).unwrap())); assert_eq!(res.events.len(), 1); let event = res.events.get(0).unwrap(); diff --git a/contracts/multisig/src/error.rs b/contracts/multisig/src/error.rs index c68fa6a2f..d3cf35d8b 100644 --- a/contracts/multisig/src/error.rs +++ b/contracts/multisig/src/error.rs @@ -39,6 +39,9 @@ pub enum ContractError { #[error("Key ID {key_id:?} already exists")] DuplicateKeyID { key_id: String }, + #[error("number of participants does not match number of public keys")] + PublicKeysMismatchParticipants, + #[error("missing public key for participant {participant}")] MissingPublicKey { participant: String }, } diff --git a/contracts/multisig/src/msg.rs b/contracts/multisig/src/msg.rs index 0be3f0139..123a10339 100644 --- a/contracts/multisig/src/msg.rs +++ b/contracts/multisig/src/msg.rs @@ -22,7 +22,7 @@ pub enum ExecuteMsg { KeyGen { key_id: String, snapshot: Snapshot, - pub_keys: HashMap, + pub_keys: HashMap, }, RegisterPublicKey { public_key: HexBinary, diff --git a/packages/axelar-wasm-std/src/threshold.rs b/packages/axelar-wasm-std/src/threshold.rs index 42b595d0e..ac53b4478 100644 --- a/packages/axelar-wasm-std/src/threshold.rs +++ b/packages/axelar-wasm-std/src/threshold.rs @@ -16,6 +16,8 @@ pub enum Error { #[cw_serde] #[derive(Copy)] +#[serde(try_from = "(Uint64, Uint64)")] +#[serde(into = "(Uint64, Uint64)")] pub struct Threshold { numerator: nonempty::Uint64, denominator: nonempty::Uint64, @@ -38,6 +40,12 @@ impl Fraction for Threshold { } } +impl From for (Uint64, Uint64) { + fn from(value: Threshold) -> Self { + (value.numerator.into(), value.denominator.into()) + } +} + impl TryFrom<(nonempty::Uint64, nonempty::Uint64)> for Threshold { type Error = Error;