diff --git a/Cargo.lock b/Cargo.lock index 7f01dedf7..4100bbd92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4657,6 +4657,7 @@ dependencies = [ "hex", "itertools 0.11.0", "k256", + "msgs-derive", "multisig", "prost 0.12.6", "report", diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 3f4d92f08..5d10e1833 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -51,6 +51,7 @@ gateway-api = { workspace = true } hex = { version = "0.4.3", default-features = false, features = [] } itertools = "0.11.0" k256 = { version = "0.13.1", features = ["ecdsa"] } +msgs-derive = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } router-api = { workspace = true } diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 787737180..223c529ac 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::permission_control; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -8,7 +9,10 @@ use error_stack::ResultExt; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG}; -use crate::{execute, query, reply}; +mod execute; +mod migrations; +mod query; +mod reply; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -24,43 +28,29 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let config = make_config(&deps, msg)?; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -fn make_config( - deps: &DepsMut, - msg: InstantiateMsg, -) -> Result { - let admin = deps.api.addr_validate(&msg.admin_address)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; - let gateway = deps.api.addr_validate(&msg.gateway_address)?; - let multisig = deps.api.addr_validate(&msg.multisig_address)?; - let coordinator = deps.api.addr_validate(&msg.coordinator_address)?; - let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; - let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; - - Ok(Config { - admin, - governance, - gateway, - multisig, - coordinator, - service_registry, - voting_verifier, + let config = Config { + gateway: deps.api.addr_validate(&msg.gateway_address)?, + multisig: deps.api.addr_validate(&msg.multisig_address)?, + coordinator: deps.api.addr_validate(&msg.coordinator_address)?, + service_registry: deps.api.addr_validate(&msg.service_registry_address)?, + voting_verifier: deps.api.addr_validate(&msg.voting_verifier_address)?, signing_threshold: msg.signing_threshold, service_name: msg.service_name, - chain_name: msg - .chain_name - .parse() - .map_err(|_| ContractError::InvalidChainName)?, + chain_name: msg.chain_name.parse()?, verifier_set_diff_threshold: msg.verifier_set_diff_threshold, encoder: msg.encoder, key_type: msg.key_type, domain_separator: msg.domain_separator, - }) + }; + CONFIG.save(deps.storage, &config)?; + + permission_control::set_admin(deps.storage, &deps.api.addr_validate(&msg.admin_address)?)?; + permission_control::set_governance( + deps.storage, + &deps.api.addr_validate(&msg.governance_address)?, + )?; + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -70,29 +60,22 @@ pub fn execute( info: MessageInfo, msg: ExecuteMsg, ) -> Result { - match msg { - ExecuteMsg::ConstructProof { message_ids } => execute::construct_proof(deps, message_ids), - ExecuteMsg::UpdateVerifierSet {} => { - execute::require_admin(&deps, info.clone()) - .or_else(|_| execute::require_governance(&deps, info))?; - execute::update_verifier_set(deps, env) + match msg.ensure_permissions(deps.storage, &info.sender)? { + ExecuteMsg::ConstructProof { message_ids } => { + Ok(execute::construct_proof(deps, message_ids)?) } + ExecuteMsg::UpdateVerifierSet {} => Ok(execute::update_verifier_set(deps, env)?), ExecuteMsg::ConfirmVerifierSet {} => Ok(execute::confirm_verifier_set(deps, info.sender)?), ExecuteMsg::UpdateSigningThreshold { new_signing_threshold, - } => { - execute::require_governance(&deps, info)?; - Ok(execute::update_signing_threshold( - deps, - new_signing_threshold, - )?) - } + } => Ok(execute::update_signing_threshold( + deps, + new_signing_threshold, + )?), ExecuteMsg::UpdateAdmin { new_admin_address } => { - execute::require_governance(&deps, info)?; Ok(execute::update_admin(deps, new_admin_address)?) } } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -131,19 +114,22 @@ pub fn migrate( _env: Env, _msg: Empty, ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + migrations::v0_6_0::migrate(deps.storage)?; + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) } #[cfg(test)] mod tests { - use axelar_wasm_std::{MajorityThreshold, Threshold, VerificationStatus}; + use axelar_wasm_std::permission_control::Permission; + use axelar_wasm_std::{permission_control, MajorityThreshold, Threshold, VerificationStatus}; use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; use cosmwasm_std::{ - from_json, Addr, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, Uint64, + from_json, Addr, Api, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, + Uint64, }; use multisig::msg::Signer; use multisig::verifier_set::VerifierSet; @@ -345,13 +331,30 @@ mod tests { 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.signing_threshold, signing_threshold); assert_eq!(config.service_name, service_name); - assert_eq!(config.encoder, encoding) + assert_eq!(config.encoder, encoding); + + assert_eq!( + permission_control::sender_role( + deps.as_ref().storage, + &deps.api.addr_validate(admin).unwrap() + ) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role( + deps.as_ref().storage, + &deps.api.addr_validate(governance).unwrap() + ) + .unwrap(), + Permission::Governance.into() + ); } } @@ -413,7 +416,11 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + axelar_wasm_std::ContractError::from(permission_control::Error::PermissionDenied { + expected: Permission::Elevated.into(), + actual: Permission::NoPrivilege.into() + }) + .to_string() ); } @@ -864,7 +871,16 @@ mod tests { let res = execute_update_admin(deps.as_mut(), GOVERNANCE, new_admin.to_string()); assert!(res.is_ok(), "{:?}", res); - let config = CONFIG.load(deps.as_ref().storage).unwrap(); - assert_eq!(config.admin, Addr::unchecked(new_admin)); + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(new_admin)) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(ADMIN)) + .unwrap(), + Permission::NoPrivilege.into() + ); } } diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/contract/execute.rs similarity index 94% rename from contracts/multisig-prover/src/execute.rs rename to contracts/multisig-prover/src/contract/execute.rs index d4d8b2a70..677ec8d55 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -1,10 +1,11 @@ use std::collections::{BTreeMap, HashSet}; +use axelar_wasm_std::permission_control::Permission; use axelar_wasm_std::snapshot::{Participant, Snapshot}; -use axelar_wasm_std::{FnExt, MajorityThreshold, VerificationStatus}; +use axelar_wasm_std::{permission_control, FnExt, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{ - to_json_binary, wasm_execute, Addr, DepsMut, Env, MessageInfo, QuerierWrapper, QueryRequest, - Response, Storage, SubMsg, WasmQuery, + to_json_binary, wasm_execute, Addr, DepsMut, Env, QuerierWrapper, QueryRequest, Response, + Storage, SubMsg, WasmQuery, }; use itertools::Itertools; use multisig::msg::Signer; @@ -19,20 +20,6 @@ use crate::state::{ Config, CONFIG, CURRENT_VERIFIER_SET, NEXT_VERIFIER_SET, PAYLOAD, REPLY_TRACKER, }; -pub fn require_admin(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - match CONFIG.load(deps.storage)?.admin { - admin if admin == info.sender => Ok(()), - _ => Err(ContractError::Unauthorized), - } -} - -pub fn require_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - match CONFIG.load(deps.storage)?.governance { - governance if governance == info.sender => Ok(()), - _ => Err(ContractError::Unauthorized), - } -} - pub fn construct_proof( deps: DepsMut, message_ids: Vec, @@ -322,7 +309,8 @@ pub fn confirm_verifier_set(deps: DepsMut, sender: Addr) -> Result Result { - CONFIG.update( - deps.storage, - |mut config| -> Result { - config.admin = deps.api.addr_validate(&new_admin_address)?; - Ok(config) - }, - )?; + let new_admin = deps.api.addr_validate(&new_admin_address)?; + permission_control::set_admin(deps.storage, &new_admin)?; Ok(Response::new()) } @@ -432,8 +415,7 @@ mod tests { use cosmwasm_std::Addr; use router_api::ChainName; - use super::{different_set_in_progress, get_next_verifier_set}; - use crate::execute::should_update_verifier_set; + use super::{different_set_in_progress, get_next_verifier_set, should_update_verifier_set}; use crate::state::{Config, NEXT_VERIFIER_SET}; use crate::test::test_data; @@ -554,8 +536,6 @@ mod tests { fn mock_config() -> Config { Config { - admin: Addr::unchecked("doesn't matter"), - governance: Addr::unchecked("doesn't matter"), gateway: Addr::unchecked("doesn't matter"), multisig: Addr::unchecked("doesn't matter"), coordinator: Addr::unchecked("doesn't matter"), diff --git a/contracts/multisig-prover/src/contract/migrations/mod.rs b/contracts/multisig-prover/src/contract/migrations/mod.rs new file mode 100644 index 000000000..b29376b44 --- /dev/null +++ b/contracts/multisig-prover/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub(crate) mod v0_6_0; diff --git a/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs new file mode 100644 index 000000000..b1786db5e --- /dev/null +++ b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs @@ -0,0 +1,218 @@ +#![allow(deprecated)] + +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::{permission_control, ContractError, MajorityThreshold}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::Item; +use multisig::key::KeyType; +use router_api::ChainName; + +use crate::contract::CONTRACT_NAME; +use crate::encoding::Encoder; +use crate::state; + +const BASE_VERSION: &str = "0.6.0"; + +pub(crate) fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + let config = CONFIG.load(storage)?; + + migrate_permission_control(storage, &config)?; + + migrate_config(storage, config)?; + Ok(()) +} + +fn migrate_permission_control( + storage: &mut dyn Storage, + config: &Config, +) -> Result<(), ContractError> { + permission_control::set_governance(storage, &config.governance)?; + permission_control::set_admin(storage, &config.admin)?; + Ok(()) +} + +fn migrate_config(storage: &mut dyn Storage, config: Config) -> Result<(), ContractError> { + CONFIG.remove(storage); + + let config = state::Config { + gateway: config.gateway, + multisig: config.multisig, + coordinator: config.coordinator, + service_registry: config.service_registry, + voting_verifier: config.voting_verifier, + signing_threshold: config.signing_threshold, + service_name: config.service_name, + chain_name: config.chain_name, + verifier_set_diff_threshold: config.verifier_set_diff_threshold, + encoder: config.encoder, + key_type: config.key_type, + domain_separator: config.domain_separator, + }; + state::CONFIG.save(storage, &config)?; + Ok(()) +} + +#[cw_serde] +#[deprecated(since = "0.6.0", note = "only used during migration")] +pub struct Config { + pub admin: Addr, + pub governance: Addr, + pub gateway: Addr, + pub multisig: Addr, + pub coordinator: Addr, + pub service_registry: Addr, + pub voting_verifier: Addr, + pub signing_threshold: MajorityThreshold, + pub service_name: String, + pub chain_name: ChainName, + pub verifier_set_diff_threshold: u32, + pub encoder: Encoder, + pub key_type: KeyType, + pub domain_separator: Hash, +} +#[deprecated(since = "0.6.0", note = "only used during migration")] +pub const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod tests { + use axelar_wasm_std::permission_control::Permission; + use axelar_wasm_std::{permission_control, MajorityThreshold, Threshold}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; + use multisig::key::KeyType; + + use crate::contract::migrations::v0_6_0; + use crate::contract::CONTRACT_NAME; + use crate::encoding::Encoder; + use crate::error::ContractError; + use crate::msg::InstantiateMsg; + use crate::state; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_6_0::BASE_VERSION) + .unwrap(); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn migrate_permission_control() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked("admin")) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked("governance")) + .unwrap(), + Permission::Governance.into() + ); + } + + #[test] + fn migrate_config() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + assert!(v0_6_0::CONFIG.load(deps.as_ref().storage).is_ok()); + assert!(state::CONFIG.load(deps.as_ref().storage).is_err()); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_6_0::CONFIG.load(deps.as_ref().storage).is_err()); + assert!(state::CONFIG.load(deps.as_ref().storage).is_ok()); + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + gateway_address: "gateway".to_string(), + multisig_address: "multisig".to_string(), + coordinator_address: "coordinator".to_string(), + service_registry_address: "service_registry".to_string(), + voting_verifier_address: "voting_verifier".to_string(), + signing_threshold: Threshold::try_from((2u64, 3u64)) + .and_then(MajorityThreshold::try_from) + .unwrap(), + service_name: "service".to_string(), + chain_name: "chain".to_string(), + verifier_set_diff_threshold: 1, + encoder: Encoder::Abi, + key_type: KeyType::Ecdsa, + domain_separator: [0; 32], + }, + ) + .unwrap(); + } + + #[deprecated(since = "0.6.0", note = "only used to test the migration")] + pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_6_0::BASE_VERSION)?; + + let config = make_config(&deps, msg)?; + v0_6_0::CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) + } + + fn make_config( + deps: &DepsMut, + msg: InstantiateMsg, + ) -> Result { + let admin = deps.api.addr_validate(&msg.admin_address)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + let gateway = deps.api.addr_validate(&msg.gateway_address)?; + let multisig = deps.api.addr_validate(&msg.multisig_address)?; + let coordinator = deps.api.addr_validate(&msg.coordinator_address)?; + let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; + let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; + + Ok(v0_6_0::Config { + admin, + governance, + gateway, + multisig, + coordinator, + service_registry, + voting_verifier, + signing_threshold: msg.signing_threshold, + service_name: msg.service_name, + chain_name: msg + .chain_name + .parse() + .map_err(|_| ContractError::InvalidChainName)?, + verifier_set_diff_threshold: msg.verifier_set_diff_threshold, + encoder: msg.encoder, + key_type: msg.key_type, + domain_separator: msg.domain_separator, + }) + } +} diff --git a/contracts/multisig-prover/src/query.rs b/contracts/multisig-prover/src/contract/query.rs similarity index 100% rename from contracts/multisig-prover/src/query.rs rename to contracts/multisig-prover/src/contract/query.rs diff --git a/contracts/multisig-prover/src/reply.rs b/contracts/multisig-prover/src/contract/reply.rs similarity index 100% rename from contracts/multisig-prover/src/reply.rs rename to contracts/multisig-prover/src/contract/reply.rs diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index 7915392a6..0e7c90113 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -8,9 +8,6 @@ pub enum ContractError { #[error(transparent)] Std(#[from] StdError), - #[error("caller is not authorized")] - Unauthorized, - #[error("message is invalid")] InvalidMessage, diff --git a/contracts/multisig-prover/src/lib.rs b/contracts/multisig-prover/src/lib.rs index aa670ca61..b59c7551a 100644 --- a/contracts/multisig-prover/src/lib.rs +++ b/contracts/multisig-prover/src/lib.rs @@ -2,11 +2,8 @@ pub mod contract; pub mod encoding; pub mod error; pub mod events; -mod execute; pub mod msg; pub mod payload; -mod query; -mod reply; pub mod state; #[cfg(test)] diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index 2505cd1ef..5a7f7001a 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -2,6 +2,7 @@ use axelar_wasm_std::hash::Hash; use axelar_wasm_std::MajorityThreshold; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{HexBinary, Uint64}; +use msgs_derive::EnsurePermissions; use multisig::key::KeyType; use router_api::CrossChainId; @@ -57,23 +58,25 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { // Start building a proof that includes specified messages // Queries the gateway for actual message contents - ConstructProof { - message_ids: Vec, - }, + #[permission(Any)] + ConstructProof { message_ids: Vec }, + #[permission(Elevated)] UpdateVerifierSet, + + #[permission(Any)] ConfirmVerifierSet, // Updates the signing threshold. The threshold currently in use does not change. // The verifier set must be updated and confirmed for the change to take effect. - // Callable only by governance. + #[permission(Governance)] UpdateSigningThreshold { new_signing_threshold: MajorityThreshold, }, - UpdateAdmin { - new_admin_address: String, - }, + #[permission(Governance)] + UpdateAdmin { new_admin_address: String }, } #[cw_serde] diff --git a/contracts/multisig-prover/src/state.rs b/contracts/multisig-prover/src/state.rs index 6891febeb..babfc35f5 100644 --- a/contracts/multisig-prover/src/state.rs +++ b/contracts/multisig-prover/src/state.rs @@ -12,8 +12,6 @@ use crate::payload::{Payload, PayloadId}; #[cw_serde] pub struct Config { - pub admin: Addr, - pub governance: Addr, pub gateway: Addr, pub multisig: Addr, pub coordinator: Addr,