Skip to content

Commit

Permalink
feat(minor-interchain-token-service): freeze/unfreeze
Browse files Browse the repository at this point in the history
* Add support for freezing and unfreezing specific chains
  • Loading branch information
cjcobb23 committed Oct 29, 2024
1 parent 4adfcef commit 0974784
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 17 deletions.
13 changes: 11 additions & 2 deletions 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,8 +30,10 @@ pub enum Error {
RegisterItsContract,
#[error("failed to deregsiter an its edge contract")]
DeregisterItsContract,
#[error("too many coins attached. Execute accepts zero or one coins")]
TooManyCoins,
#[error("failed to freeze chain")]
FreezeChain,
#[error("failed to unfreeze chain")]
UnfreezeChain,
#[error("failed to query its address")]
QueryItsContract,
#[error("failed to query all its addresses")]
Expand Down Expand Up @@ -99,6 +102,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)
}
}?
.then(Ok)
}
Expand Down
224 changes: 223 additions & 1 deletion 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 @@ -27,6 +27,12 @@ pub enum Error {
NexusQueryError,
#[error("storage error")]
StorageError,
#[error("failed to freeze chain")]
FailedToFreezeChain,
#[error("failed to unfreeze chain")]
FailedToUnfreezeChain,
#[error("chain {0} is frozen")]
ChainFrozen(ChainNameRaw),
}

/// Executes an incoming ITS message.
Expand All @@ -41,12 +47,22 @@ pub fn execute_message(
payload: HexBinary,
) -> Result<Response, Error> {
ensure_its_source_address(deps.storage, &cc_id.source_chain, &source_address)?;
ensure!(
!is_chain_frozen(deps.storage, &cc_id.source_chain)
.change_context(Error::FailedExecuteMessage)?,
Error::ChainFrozen(cc_id.source_chain)
);

match HubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? {
HubMessage::SendToHub {
destination_chain,
message,
} => {
ensure!(
!is_chain_frozen(deps.storage, &destination_chain)
.change_context(Error::FailedExecuteMessage)?,
Error::ChainFrozen(destination_chain)
);
let destination_address = load_its_contract(deps.storage, &destination_chain)
.change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?;

Expand Down Expand Up @@ -132,3 +148,209 @@ 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::FailedToFreezeChain)?;

Ok(Response::new())
}

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

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 crate::contract::execute::{
execute_message, freeze_chain, register_its_contract, 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";

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 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,
&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
));

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(),
));
}
}
}
28 changes: 21 additions & 7 deletions contracts/interchain-token-service/src/contract/query.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
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 load ITS contract")]
ItsContract,
#[error("failed to load all ITS contracts")]
AllItsContracts,
#[error("failed to serialize data to JSON")]
JsonSerializationError,
}

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_contracts(deps: Deps, chain: ChainNameRaw) -> Result<Binary, Error> {
let contract_address =
may_load_its_contract(deps.storage, &chain).change_context(Error::ItsContract)?;
to_json_binary(&contract_address).change_context(Error::JsonSerializationError)
}

pub fn all_its_contracts(deps: Deps) -> Result<Binary, Error> {
let contract_addresses =
load_all_its_contracts(deps.storage).change_context(Error::AllItsContracts)?;
to_json_binary(&contract_addresses).change_context(Error::JsonSerializationError)
}
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 @@ -33,6 +33,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)]
FreezeChain { chain: ChainNameRaw },

#[permission(Elevated)]
UnfreezeChain { chain: ChainNameRaw },
}

#[cw_serde]
Expand Down
Loading

0 comments on commit 0974784

Please sign in to comment.