Skip to content

Commit

Permalink
feat(minor-interchain-token-service): freeze/unfreeze (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcobb23 authored Oct 31, 2024
1 parent a6952bb commit 03f7fb4
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 41 deletions.
13 changes: 12 additions & 1 deletion contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use axelarnet_gateway::AxelarExecutableMsg;
use cosmwasm_std::entry_point;
use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage};
use error_stack::{Report, ResultExt};
use execute::{freeze_chain, unfreeze_chain};

use crate::events::Event;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
Expand All @@ -29,6 +30,10 @@ pub enum Error {
RegisterItsContract,
#[error("failed to deregsiter an its edge contract")]
DeregisterItsContract,
#[error("failed to freeze chain")]
FreezeChain,
#[error("failed to unfreeze chain")]
UnfreezeChain,
#[error("failed to set chain config")]
SetChainConfig,
#[error("failed to query its address")]
Expand Down Expand Up @@ -105,6 +110,12 @@ pub fn execute(
execute::deregister_its_contract(deps, chain)
.change_context(Error::DeregisterItsContract)
}
ExecuteMsg::FreezeChain { chain } => {
freeze_chain(deps, chain).change_context(Error::FreezeChain)
}
ExecuteMsg::UnfreezeChain { chain } => {
unfreeze_chain(deps, chain).change_context(Error::UnfreezeChain)
}
ExecuteMsg::DisableExecution => {
execute::disable_execution(deps).change_context(Error::DisableExecution)
}
Expand All @@ -129,7 +140,7 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result<Addr, Report<E
pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, ContractError> {
match msg {
QueryMsg::ItsContract { chain } => {
query::its_contracts(deps, chain).change_context(Error::QueryItsContract)
query::its_contract(deps, chain).change_context(Error::QueryItsContract)
}
QueryMsg::AllItsContracts => {
query::all_its_contracts(deps).change_context(Error::QueryAllItsContracts)
Expand Down
197 changes: 181 additions & 16 deletions contracts/interchain-token-service/src/contract/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use router_api::{Address, ChainName, ChainNameRaw, CrossChainId};

use crate::events::Event;
use crate::primitives::HubMessage;
use crate::state::{self, load_config, load_its_contract};
use crate::state::{self, is_chain_frozen, load_config, load_its_contract};

#[derive(thiserror::Error, Debug, IntoContractError)]
pub enum Error {
Expand All @@ -31,6 +31,8 @@ pub enum Error {
LoadChainConfig(ChainNameRaw),
#[error("failed to save chain config for chain {0}")]
SaveChainConfig(ChainNameRaw),
#[error("chain {0} is frozen")]
ChainFrozen(ChainNameRaw),
#[error("state error")]
State,
}
Expand Down Expand Up @@ -60,6 +62,8 @@ pub fn execute_message(
let destination_address = load_its_contract(deps.storage, &destination_chain)
.change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?;

verify_chains_not_frozen(deps.storage, &cc_id.source_chain, &destination_chain)?;

let destination_payload = HubMessage::ReceiveFromHub {
source_chain: cc_id.source_chain.clone(),
message: message.clone(),
Expand All @@ -86,6 +90,19 @@ pub fn execute_message(
}
}

fn verify_chains_not_frozen(
storage: &dyn Storage,
source_chain: &ChainNameRaw,
destination_chain: &ChainNameRaw,
) -> Result<(), Error> {
for chain in [source_chain, destination_chain] {
if is_chain_frozen(storage, chain).change_context(Error::State)? {
return Err(report!(Error::ChainFrozen(chain.to_owned())));
}
}
Ok(())
}

fn normalize(chain: &ChainNameRaw) -> ChainName {
ChainName::try_from(chain.as_ref()).expect("invalid chain name")
}
Expand Down Expand Up @@ -143,6 +160,18 @@ pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result<Res
Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into()))
}

pub fn freeze_chain(deps: DepsMut, chain: ChainNameRaw) -> Result<Response, Error> {
state::freeze_chain(deps.storage, &chain).change_context(Error::State)?;

Ok(Response::new())
}

pub fn unfreeze_chain(deps: DepsMut, chain: ChainNameRaw) -> Result<Response, Error> {
state::unfreeze_chain(deps.storage, &chain).change_context(Error::State)?;

Ok(Response::new())
}

pub fn disable_execution(deps: DepsMut) -> Result<Response, Error> {
killswitch::engage(deps.storage, Event::ExecutionDisabled).change_context(Error::State)
}
Expand Down Expand Up @@ -176,15 +205,16 @@ mod tests {
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,
disable_execution, enable_execution, execute_message, freeze_chain, register_its_contract,
set_chain_config, unfreeze_chain, Error,
};
use crate::state::{self, Config};
use crate::{HubMessage, Message};

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

const ITS_ADDRESS: &str = "68d30f47F19c07bCCEf4Ac7FAE2Dc12FCa3e0dC9";

Expand Down Expand Up @@ -246,6 +276,140 @@ mod tests {
));
}

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

let source_chain = ChainNameRaw::try_from(SOLANA).unwrap();
let destination_chain = ChainNameRaw::try_from(ETHEREUM).unwrap();

assert_ok!(freeze_chain(deps.as_mut(), source_chain.clone()));

let msg = HubMessage::SendToHub {
destination_chain,
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: source_chain.clone(),
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::ChainFrozen(..));

assert_ok!(unfreeze_chain(deps.as_mut(), source_chain.clone()));

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

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

let source_chain = ChainNameRaw::try_from(SOLANA).unwrap();
let destination_chain = ChainNameRaw::try_from(ETHEREUM).unwrap();

assert_ok!(freeze_chain(deps.as_mut(), destination_chain.clone()));

let msg = HubMessage::SendToHub {
destination_chain: destination_chain.clone(),
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 cc_id = CrossChainId {
source_chain,
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32)
.to_string()
.try_into()
.unwrap(),
};

let res = execute_message(
deps.as_mut(),
cc_id.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
msg.clone().abi_encode(),
);
assert_err_contains!(res, Error, Error::ChainFrozen(..));

assert_ok!(unfreeze_chain(deps.as_mut(), destination_chain));

assert_ok!(execute_message(
deps.as_mut(),
cc_id,
ITS_ADDRESS.to_string().try_into().unwrap(),
msg.clone().abi_encode(),
));
}

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

let source_chain = ChainNameRaw::try_from(SOLANA).unwrap();
let destination_chain = ChainNameRaw::try_from(ETHEREUM).unwrap();
let other_chain = ChainNameRaw::try_from(XRPL).unwrap();

assert_ok!(freeze_chain(deps.as_mut(), other_chain.clone()));

let msg = HubMessage::SendToHub {
destination_chain: destination_chain.clone(),
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 cc_id = CrossChainId {
source_chain,
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32)
.to_string()
.try_into()
.unwrap(),
};

assert_ok!(execute_message(
deps.as_mut(),
cc_id.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
msg.clone().abi_encode(),
));
}

fn init(deps: &mut OwnedDeps<MemoryStorage, MockApi, MockQuerier>) {
assert_ok!(permission_control::set_admin(
deps.as_mut().storage,
Expand All @@ -267,19 +431,20 @@ mod tests {
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(),
));
for chain_name in [SOLANA, ETHEREUM, XRPL] {
let chain = ChainNameRaw::try_from(chain_name).unwrap();
assert_ok!(register_its_contract(
deps.as_mut(),
chain.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
));
assert_ok!(set_chain_config(
deps.as_mut(),
chain,
Uint256::one().try_into().unwrap(),
16u8
));
}
}
}
25 changes: 18 additions & 7 deletions contracts/interchain-token-service/src/contract/query.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
use axelar_wasm_std::IntoContractError;
use cosmwasm_std::{to_json_binary, Binary, Deps};
use error_stack::{Result, ResultExt};
use router_api::ChainNameRaw;

use crate::state;
use crate::state::{load_all_its_contracts, may_load_its_contract};

pub fn its_contracts(deps: Deps, chain: ChainNameRaw) -> Result<Binary, state::Error> {
let contract_address = state::may_load_its_contract(deps.storage, &chain)?;
Ok(to_json_binary(&contract_address)?)
#[derive(thiserror::Error, Debug, IntoContractError)]
pub enum Error {
#[error("failed to serialize data to JSON")]
JsonSerialization,
#[error("state error")]
State,
}

pub fn all_its_contracts(deps: Deps) -> Result<Binary, state::Error> {
let contract_addresses = state::load_all_its_contracts(deps.storage)?;
Ok(to_json_binary(&contract_addresses)?)
pub fn its_contract(deps: Deps, chain: ChainNameRaw) -> Result<Binary, Error> {
let contract_address =
may_load_its_contract(deps.storage, &chain).change_context(Error::State)?;
to_json_binary(&contract_address).change_context(Error::JsonSerialization)
}

pub fn all_its_contracts(deps: Deps) -> Result<Binary, Error> {
let contract_addresses = load_all_its_contracts(deps.storage).change_context(Error::State)?;
to_json_binary(&contract_addresses).change_context(Error::JsonSerialization)
}
8 changes: 8 additions & 0 deletions contracts/interchain-token-service/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ pub enum ExecuteMsg {
#[permission(Elevated)]
DeregisterItsContract { chain: ChainNameRaw },

/// Freeze execution of ITS messages for a particular chain
#[permission(Elevated)]
FreezeChain { chain: ChainNameRaw },

/// Unfreeze execution of ITS messages for a particular chain
#[permission(Elevated)]
UnfreezeChain { chain: ChainNameRaw },

#[permission(Elevated)]
DisableExecution,

Expand Down
Loading

0 comments on commit 03f7fb4

Please sign in to comment.