Skip to content

Commit

Permalink
feat(minor-multisig)!: authorize and unauthorize vector of addresses (a…
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcobb23 authored Jul 11, 2024
1 parent c60c1c9 commit 6d4a31b
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 115 deletions.
293 changes: 229 additions & 64 deletions contracts/multisig/src/contract.rs

Large diffs are not rendered by default.

66 changes: 49 additions & 17 deletions contracts/multisig/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use cosmwasm_std::{ensure, OverflowError, OverflowOperation, WasmMsg};
use std::collections::HashMap;

use cosmwasm_std::{ensure, OverflowError, OverflowOperation, Storage, WasmMsg};
use router_api::ChainName;
use sha3::{Digest, Keccak256};
use signature_verifier_api::client::SignatureVerifier;
Expand All @@ -11,7 +13,6 @@ use crate::{
signing::SigningSession,
state::AUTHORIZED_CALLERS,
};
use error_stack::ResultExt;

use super::*;

Expand Down Expand Up @@ -182,27 +183,58 @@ pub fn register_pub_key(
}

pub fn require_authorized_caller(
deps: &DepsMut,
contract_address: Addr,
) -> error_stack::Result<(), ContractError> {
AUTHORIZED_CALLERS
.load(deps.storage, &contract_address)
.change_context(ContractError::Unauthorized)
storage: &dyn Storage,
contract_address: &Addr,
chain_name: &ChainName,
) -> Result<Addr, ContractError> {
let expected_chain_name = AUTHORIZED_CALLERS.load(storage, contract_address)?;
if expected_chain_name != *chain_name {
return Err(ContractError::WrongChainName {
expected: expected_chain_name,
});
}
Ok(contract_address.clone())
}

pub fn authorize_caller(deps: DepsMut, contract_address: Addr) -> Result<Response, ContractError> {
AUTHORIZED_CALLERS.save(deps.storage, &contract_address, &())?;

Ok(Response::new().add_event(Event::CallerAuthorized { contract_address }.into()))
pub fn authorize_callers(
deps: DepsMut,
contracts: HashMap<Addr, ChainName>,
) -> Result<Response, ContractError> {
contracts
.iter()
.map(|(contract_address, chain_name)| {
AUTHORIZED_CALLERS.save(deps.storage, contract_address, chain_name)
})
.try_collect()?;

Ok(
Response::new().add_events(contracts.into_iter().map(|(contract_address, chain_name)| {
Event::CallerAuthorized {
contract_address,
chain_name,
}
.into()
})),
)
}

pub fn unauthorize_caller(
pub fn unauthorize_callers(
deps: DepsMut,
contract_address: Addr,
contracts: Vec<(Addr, ChainName)>,
) -> Result<Response, ContractError> {
AUTHORIZED_CALLERS.remove(deps.storage, &contract_address);

Ok(Response::new().add_event(Event::CallerUnauthorized { contract_address }.into()))
contracts.iter().for_each(|(contract_address, _)| {
AUTHORIZED_CALLERS.remove(deps.storage, contract_address)
});

Ok(
Response::new().add_events(contracts.into_iter().map(|(contract_address, chain_name)| {
Event::CallerUnauthorized {
contract_address,
chain_name,
}
.into()
})),
)
}

pub fn enable_signing(deps: DepsMut) -> Result<Response, ContractError> {
Expand Down
61 changes: 55 additions & 6 deletions contracts/multisig/src/contract/migrations/v0_4_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, StdError, Storage};
use cw2::VersionError;
use cw_storage_plus::Item;
use itertools::Itertools;
use router_api::ChainName;

use crate::contract::CONTRACT_NAME;
use crate::state::AUTHORIZED_CALLERS;
use axelar_wasm_std::killswitch::State;
use axelar_wasm_std::{killswitch, nonempty, permission_control};

Expand All @@ -21,7 +24,11 @@ pub enum Error {
NonEmpty(#[from] nonempty::Error),
}

pub fn migrate(storage: &mut dyn Storage, admin: Addr) -> Result<(), Error> {
pub fn migrate(
storage: &mut dyn Storage,
admin: Addr,
authorized_callers: Vec<(Addr, ChainName)>,
) -> Result<(), Error> {
cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?;

killswitch::init(storage, State::Disengaged)?;
Expand All @@ -30,6 +37,21 @@ pub fn migrate(storage: &mut dyn Storage, admin: Addr) -> Result<(), Error> {
permission_control::set_governance(storage, &config.governance)?;
permission_control::set_admin(storage, &admin)?;
migrate_config(storage, config)?;
migrate_authorized_callers(storage, authorized_callers)?;
Ok(())
}

fn migrate_authorized_callers(
storage: &mut dyn Storage,
authorized_callers: Vec<(Addr, ChainName)>,
) -> Result<(), Error> {
AUTHORIZED_CALLERS.clear(storage);
authorized_callers
.iter()
.map(|(contract_address, chain_name)| {
AUTHORIZED_CALLERS.save(storage, contract_address, chain_name)
})
.try_collect()?;
Ok(())
}

Expand Down Expand Up @@ -71,10 +93,11 @@ mod tests {
use cosmwasm_std::{Addr, DepsMut, Env, HexBinary, MessageInfo, Response, Uint64};

use axelar_wasm_std::nonempty;
use router_api::ChainName;

use crate::contract::migrations::v0_4_1;
use crate::contract::migrations::v0_4_1::BASE_VERSION;
use crate::contract::{execute, CONTRACT_NAME};
use crate::contract::{execute, query, CONTRACT_NAME};
use crate::msg::ExecuteMsg::{DisableSigning, SubmitSignature};
use crate::state::SIGNING_SESSION_COUNTER;
use crate::ContractError;
Expand All @@ -96,11 +119,11 @@ mod tests {

cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap();

assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin")).is_err());
assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_err());

cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, BASE_VERSION).unwrap();

assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin")).is_ok());
assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok());
}

#[test]
Expand All @@ -118,7 +141,7 @@ mod tests {
)
.unwrap();

assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin")).is_ok());
assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok());

assert!(v0_4_1::CONFIG.load(deps.as_mut().storage).is_err());

Expand Down Expand Up @@ -146,7 +169,7 @@ mod tests {
)
.unwrap();

assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin")).is_ok());
assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok());

// contract is enabled
assert!(!execute(
Expand Down Expand Up @@ -199,6 +222,32 @@ mod tests {
.contains(&ContractError::SigningDisabled.to_string()));
}

#[test]
fn callers_are_authorized() {
let mut deps = mock_dependencies();
instantiate(
deps.as_mut(),
mock_env(),
mock_info("admin", &[]),
InstantiateMsg {
governance_address: "governance".to_string(),
rewards_address: "rewards".to_string(),
block_expiry: 100,
},
)
.unwrap();

let prover = Addr::unchecked("prover1");
let chain_name: ChainName = "mock-chain".parse().unwrap();
assert!(v0_4_1::migrate(
deps.as_mut().storage,
Addr::unchecked("admin"),
vec![(prover.clone(), chain_name.clone())]
)
.is_ok());
assert!(query::caller_authorized(deps.as_ref(), prover, chain_name).unwrap());
}

#[deprecated(since = "0.4.1", note = "only used to test migration")]
fn instantiate(
deps: DepsMut,
Expand Down
8 changes: 4 additions & 4 deletions contracts/multisig/src/contract/query.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use router_api::ChainName;

use crate::{
key::{KeyType, PublicKey},
multisig::Multisig,
Expand Down Expand Up @@ -29,9 +31,7 @@ pub fn get_public_key(deps: Deps, verifier: Addr, key_type: KeyType) -> StdResul
Ok(PublicKey::try_from((key_type, raw)).expect("could not decode pub key"))
}

pub fn caller_authorized(deps: Deps, address: Addr) -> StdResult<bool> {
let is_authorized = AUTHORIZED_CALLERS
.may_load(deps.storage, &address)?
.is_some();
pub fn caller_authorized(deps: Deps, address: Addr, chain_name: ChainName) -> StdResult<bool> {
let is_authorized = AUTHORIZED_CALLERS.may_load(deps.storage, &address)? == Some(chain_name);
Ok(is_authorized)
}
4 changes: 4 additions & 0 deletions contracts/multisig/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use axelar_wasm_std_derive::IntoContractError;
use cosmwasm_std::{OverflowError, StdError, Uint64};
use cw2::VersionError;
use router_api::ChainName;
use thiserror::Error;

#[derive(Error, Debug, PartialEq, IntoContractError)]
Expand Down Expand Up @@ -67,4 +68,7 @@ pub enum ContractError {

#[error("signing is disabled")]
SigningDisabled,

#[error("specified chain name is incorrect. expected: {expected}")]
WrongChainName { expected: ChainName },
}
22 changes: 14 additions & 8 deletions contracts/multisig/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ pub enum Event {
},
CallerAuthorized {
contract_address: Addr,
chain_name: ChainName,
},
CallerUnauthorized {
contract_address: Addr,
chain_name: ChainName,
},
SigningEnabled,
SigningDisabled,
Expand Down Expand Up @@ -91,14 +93,18 @@ impl From<Event> for cosmwasm_std::Event {
"public_key",
to_string(&public_key).expect("failed to serialize public key"),
),
Event::CallerAuthorized { contract_address } => {
cosmwasm_std::Event::new("caller_authorized")
.add_attribute("contract_address", contract_address)
}
Event::CallerUnauthorized { contract_address } => {
cosmwasm_std::Event::new("caller_unauthorized")
.add_attribute("contract_address", contract_address)
}
Event::CallerAuthorized {
contract_address,
chain_name,
} => cosmwasm_std::Event::new("caller_authorized")
.add_attribute("contract_address", contract_address)
.add_attribute("chain_name", chain_name),
Event::CallerUnauthorized {
contract_address,
chain_name,
} => cosmwasm_std::Event::new("caller_unauthorized")
.add_attribute("contract_address", contract_address)
.add_attribute("chain_name", chain_name),
Event::SigningEnabled => cosmwasm_std::Event::new("signing_enabled"),
Event::SigningDisabled => cosmwasm_std::Event::new("signing_disabled"),
}
Expand Down
37 changes: 24 additions & 13 deletions contracts/multisig/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use axelar_wasm_std::nonempty;
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, HexBinary, Uint128, Uint64};
Expand All @@ -13,23 +15,25 @@ use crate::{
#[cw_serde]
pub struct MigrationMsg {
pub admin_address: String,
pub authorized_callers: HashMap<String, ChainName>,
}

#[cw_serde]
pub struct InstantiateMsg {
// the governance address is allowed to modify the authorized caller list for this contract
/// the governance address is allowed to modify the authorized caller list for this contract
pub governance_address: String,
// The admin address (or governance) is allowed to disable signing. Only governance can re-enable
/// The admin address (or governance) is allowed to disable signing and enable signing
pub admin_address: String,
pub rewards_address: String,
pub block_expiry: nonempty::Uint64, // number of blocks after which a signing session expires
/// number of blocks after which a signing session expires
pub block_expiry: nonempty::Uint64,
}

#[cw_serde]
#[derive(EnsurePermissions)]
pub enum ExecuteMsg {
// Can only be called by an authorized contract.
#[permission(Any)]
/// Can only be called by an authorized contract.
#[permission(Specific(authorized))]
StartSigningSession {
verifier_set_id: String,
msg: HexBinary,
Expand All @@ -52,16 +56,20 @@ pub enum ExecuteMsg {
#[permission(Any)]
RegisterPublicKey {
public_key: PublicKey,
/* To prevent anyone from registering a public key that belongs to someone else, we require the sender
to sign their own address using the private key */
/// To prevent anyone from registering a public key that belongs to someone else, we require the sender
/// to sign their own address using the private key
signed_sender_address: HexBinary,
},
// Authorizes a contract to call StartSigningSession. Callable only by governance
/// Authorizes a set of contracts to call StartSigningSession.
#[permission(Governance)]
AuthorizeCaller { contract_address: Addr },
// Unauthorizes a contract, so it can no longer call StartSigningSession. Callable only by governance
#[permission(Governance)]
UnauthorizeCaller { contract_address: Addr },
AuthorizeCallers {
contracts: HashMap<String, ChainName>,
},
/// Unauthorizes a set of contracts, so they can no longer call StartSigningSession.
#[permission(Elevated)]
UnauthorizeCallers {
contracts: HashMap<String, ChainName>,
},

/// Emergency command to stop all amplifier signing
#[permission(Elevated)]
Expand All @@ -88,7 +96,10 @@ pub enum QueryMsg {
},

#[returns(bool)]
IsCallerAuthorized { contract_address: Addr },
IsCallerAuthorized {
contract_address: String,
chain_name: ChainName,
},
}

#[cw_serde]
Expand Down
3 changes: 2 additions & 1 deletion contracts/multisig/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use axelar_wasm_std::nonempty;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, HexBinary, Order, StdResult, Storage, Uint64};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex};
use router_api::ChainName;

#[cw_serde]
pub struct Config {
Expand Down Expand Up @@ -110,7 +111,7 @@ pub fn save_pub_key(
}

// The keys represent the addresses that can start a signing session.
pub const AUTHORIZED_CALLERS: Map<&Addr, ()> = Map::new("authorized_callers");
pub const AUTHORIZED_CALLERS: Map<&Addr, ChainName> = Map::new("authorized_callers");

#[cfg(test)]
mod tests {
Expand Down
7 changes: 5 additions & 2 deletions integration-tests/tests/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,8 +684,11 @@ pub fn setup_chain(
let response = protocol.multisig.execute(
&mut protocol.app,
protocol.governance_address.clone(),
&multisig::msg::ExecuteMsg::AuthorizeCaller {
contract_address: multisig_prover.contract_addr.clone(),
&multisig::msg::ExecuteMsg::AuthorizeCallers {
contracts: HashMap::from([(
multisig_prover.contract_addr.to_string(),
chain_name.clone(),
)]),
},
);
assert!(response.is_ok());
Expand Down
Loading

0 comments on commit 6d4a31b

Please sign in to comment.