Skip to content

Commit

Permalink
feat(minor-its-hub): add support for global freeze (#668)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcobb23 authored Oct 30, 2024
1 parent dc20405 commit a6952bb
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 4 deletions.
14 changes: 13 additions & 1 deletion contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::Debug;

use axelar_wasm_std::error::ContractError;
use axelar_wasm_std::{address, permission_control, FnExt, IntoContractError};
use axelar_wasm_std::{address, killswitch, permission_control, FnExt, IntoContractError};
use axelarnet_gateway::AxelarExecutableMsg;
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
Expand Down Expand Up @@ -35,6 +35,10 @@ pub enum Error {
QueryItsContract,
#[error("failed to query all its addresses")]
QueryAllItsContracts,
#[error("failed to disable execution")]
DisableExecution,
#[error("failed to enable execution")]
EnableExecution,
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down Expand Up @@ -70,6 +74,8 @@ pub fn instantiate(
state::save_its_contract(deps.storage, chain, address)?;
}

killswitch::init(deps.storage, killswitch::State::Disengaged)?;

Ok(Response::new().add_events(
msg.its_contracts
.into_iter()
Expand Down Expand Up @@ -99,6 +105,12 @@ pub fn execute(
execute::deregister_its_contract(deps, chain)
.change_context(Error::DeregisterItsContract)
}
ExecuteMsg::DisableExecution => {
execute::disable_execution(deps).change_context(Error::DisableExecution)
}
ExecuteMsg::EnableExecution => {
execute::enable_execution(deps).change_context(Error::EnableExecution)
}
ExecuteMsg::SetChainConfig {
chain,
max_uint,
Expand Down
139 changes: 137 additions & 2 deletions contracts/interchain-token-service/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use axelar_wasm_std::{nonempty, FnExt, IntoContractError};
use axelar_wasm_std::{killswitch, nonempty, FnExt, IntoContractError};
use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage};
use error_stack::{bail, ensure, report, Result, ResultExt};
use router_api::{Address, ChainName, ChainNameRaw, CrossChainId};
Expand All @@ -21,12 +21,18 @@ pub enum Error {
FailedItsContractRegistration(ChainNameRaw),
#[error("failed to deregister its contract for chain {0}")]
FailedItsContractDeregistration(ChainNameRaw),
#[error("failed to execute message")]
FailedExecuteMessage,
#[error("execution is currently disabled")]
ExecutionDisabled,
#[error("chain config for {0} already set")]
ChainConfigAlreadySet(ChainNameRaw),
#[error("invalid chain max uint")]
#[error("failed to load chain config for chain {0}")]
LoadChainConfig(ChainNameRaw),
#[error("failed to save chain config for chain {0}")]
SaveChainConfig(ChainNameRaw),
#[error("state error")]
State,
}

/// Executes an incoming ITS message.
Expand All @@ -40,6 +46,10 @@ pub fn execute_message(
source_address: Address,
payload: HexBinary,
) -> Result<Response, Error> {
ensure!(
killswitch::is_contract_active(deps.storage),
Error::ExecutionDisabled
);
ensure_its_source_address(deps.storage, &cc_id.source_chain, &source_address)?;

match HubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? {
Expand Down Expand Up @@ -133,6 +143,14 @@ pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result<Res
Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into()))
}

pub fn disable_execution(deps: DepsMut) -> Result<Response, Error> {
killswitch::engage(deps.storage, Event::ExecutionDisabled).change_context(Error::State)
}

pub fn enable_execution(deps: DepsMut) -> Result<Response, Error> {
killswitch::disengage(deps.storage, Event::ExecutionEnabled).change_context(Error::State)
}

pub fn set_chain_config(
deps: DepsMut,
chain: ChainNameRaw,
Expand All @@ -148,3 +166,120 @@ pub fn set_chain_config(
.then(|_| Ok(Response::new())),
}
}

#[cfg(test)]
mod tests {
use assert_ok::assert_ok;
use axelar_wasm_std::msg_id::HexTxHashAndEventIndex;
use axelar_wasm_std::{assert_err_contains, killswitch, nonempty, permission_control};
use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier};
use cosmwasm_std::{Addr, HexBinary, MemoryStorage, OwnedDeps, Uint256};
use router_api::{ChainNameRaw, CrossChainId};

use super::disable_execution;
use crate::contract::execute::{
enable_execution, execute_message, register_its_contract, Error,
};
use crate::state::{self, Config};
use crate::{HubMessage, Message};

const SOLANA: &str = "solana";
const ETHEREUM: &str = "ethereum";

const ITS_ADDRESS: &str = "68d30f47F19c07bCCEf4Ac7FAE2Dc12FCa3e0dC9";

const ADMIN: &str = "admin";
const GOVERNANCE: &str = "governance";
const AXELARNET_GATEWAY: &str = "axelarnet-gateway";

fn its_address() -> nonempty::HexBinary {
HexBinary::from_hex(ITS_ADDRESS)
.unwrap()
.try_into()
.unwrap()
}

#[test]
fn should_be_able_to_disable_and_enable_execution() {
let mut deps = mock_dependencies();
init(&mut deps);

assert_ok!(disable_execution(deps.as_mut()));

let msg = HubMessage::SendToHub {
destination_chain: ChainNameRaw::try_from(SOLANA).unwrap(),
message: Message::InterchainTransfer {
token_id: [7u8; 32].into(),
source_address: its_address(),
destination_address: its_address(),
amount: Uint256::one().try_into().unwrap(),
data: None,
},
};
let res = execute_message(
deps.as_mut(),
CrossChainId {
source_chain: ChainNameRaw::try_from(SOLANA).unwrap(),
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32)
.to_string()
.try_into()
.unwrap(),
},
ITS_ADDRESS.to_string().try_into().unwrap(),
msg.clone().abi_encode(),
);
assert_err_contains!(res, Error, Error::ExecutionDisabled);

assert_ok!(enable_execution(deps.as_mut()));

assert_ok!(execute_message(
deps.as_mut(),
CrossChainId {
source_chain: ChainNameRaw::try_from(SOLANA).unwrap(),
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32)
.to_string()
.try_into()
.unwrap(),
},
ITS_ADDRESS.to_string().try_into().unwrap(),
msg.abi_encode(),
));
}

fn init(deps: &mut OwnedDeps<MemoryStorage, MockApi, MockQuerier>) {
assert_ok!(permission_control::set_admin(
deps.as_mut().storage,
&Addr::unchecked(ADMIN)
));
assert_ok!(permission_control::set_governance(
deps.as_mut().storage,
&Addr::unchecked(GOVERNANCE)
));

assert_ok!(state::save_config(
deps.as_mut().storage,
&Config {
axelarnet_gateway: Addr::unchecked(AXELARNET_GATEWAY),
},
));

assert_ok!(killswitch::init(
deps.as_mut().storage,
killswitch::State::Disengaged
));
let amplifier_chain = ChainNameRaw::try_from(SOLANA).unwrap();
let core_chain = ChainNameRaw::try_from(ETHEREUM).unwrap();

assert_ok!(register_its_contract(
deps.as_mut(),
core_chain.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
));

assert_ok!(register_its_contract(
deps.as_mut(),
amplifier_chain.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
));
}
}
4 changes: 4 additions & 0 deletions contracts/interchain-token-service/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub enum Event {
ItsContractDeregistered {
chain: ChainNameRaw,
},
ExecutionDisabled,
ExecutionEnabled,
}

impl From<Event> for cosmwasm_std::Event {
Expand All @@ -35,6 +37,8 @@ impl From<Event> for cosmwasm_std::Event {
cosmwasm_std::Event::new("its_contract_deregistered")
.add_attribute("chain", chain.to_string())
}
Event::ExecutionDisabled => cosmwasm_std::Event::new("execution_disabled"),
Event::ExecutionEnabled => cosmwasm_std::Event::new("execution_enabled"),
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions contracts/interchain-token-service/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ pub enum ExecuteMsg {
/// The admin is allowed to remove the ITS address of a chain for emergencies.
#[permission(Elevated)]
DeregisterItsContract { chain: ChainNameRaw },

#[permission(Elevated)]
DisableExecution,

#[permission(Elevated)]
EnableExecution,
/// Set the chain configuration for a chain.
#[permission(Governance)]
SetChainConfig {
Expand Down
78 changes: 77 additions & 1 deletion contracts/interchain-token-service/tests/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use interchain_token_service::events::Event;
use interchain_token_service::msg::ExecuteMsg;
use interchain_token_service::{HubMessage, Message, TokenId, TokenManagerType};
use router_api::{Address, ChainName, ChainNameRaw, CrossChainId};
use utils::{make_deps, TestMessage};
use utils::{make_deps, params, TestMessage};

mod utils;

Expand Down Expand Up @@ -317,6 +317,82 @@ fn execute_message_when_invalid_message_type_fails() {
assert_err_contains!(result, ExecuteError, ExecuteError::InvalidMessageType);
}

#[test]
fn disable_execution_when_not_admin_fails() {
let mut deps = mock_dependencies();
utils::instantiate_contract(deps.as_mut()).unwrap();

let result = contract::execute(
deps.as_mut(),
mock_env(),
mock_info("not-admin", &[]),
ExecuteMsg::DisableExecution,
);
assert_err_contains!(
result,
permission_control::Error,
permission_control::Error::PermissionDenied { .. }
);
}

#[test]
fn enable_execution_when_not_admin_fails() {
let mut deps = mock_dependencies();
utils::instantiate_contract(deps.as_mut()).unwrap();

let result = contract::execute(
deps.as_mut(),
mock_env(),
mock_info("not-admin", &[]),
ExecuteMsg::EnableExecution,
);
assert_err_contains!(
result,
permission_control::Error,
permission_control::Error::PermissionDenied { .. }
);
}

#[test]
fn admin_or_governance_can_enable_execution() {
let mut deps = mock_dependencies();
utils::instantiate_contract(deps.as_mut()).unwrap();

assert_ok!(contract::execute(
deps.as_mut(),
mock_env(),
mock_info(params::ADMIN, &[]),
ExecuteMsg::EnableExecution
));

assert_ok!(contract::execute(
deps.as_mut(),
mock_env(),
mock_info(params::GOVERNANCE, &[]),
ExecuteMsg::EnableExecution
));
}

#[test]
fn admin_or_governance_can_disable_execution() {
let mut deps = mock_dependencies();
utils::instantiate_contract(deps.as_mut()).unwrap();

assert_ok!(contract::execute(
deps.as_mut(),
mock_env(),
mock_info(params::ADMIN, &[]),
ExecuteMsg::DisableExecution
));

assert_ok!(contract::execute(
deps.as_mut(),
mock_env(),
mock_info(params::GOVERNANCE, &[]),
ExecuteMsg::DisableExecution
));
}

#[test]
fn set_chain_config_should_succeed() {
let chain = "ethereum".parse().unwrap();
Expand Down

0 comments on commit a6952bb

Please sign in to comment.