Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(minor-interchain-token-service): freeze/unfreeze #670

Merged
merged 5 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion contracts/interchain-token-service/src/contract.rs
cjcobb23 marked this conversation as resolved.
Show resolved Hide resolved
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
191 changes: 175 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 All @@ -57,6 +59,8 @@ pub fn execute_message(
destination_chain,
message,
} => {
verify_chains_not_frozen(deps.storage, &cc_id.source_chain, &destination_chain)?;

let destination_address = load_its_contract(deps.storage, &destination_chain)
.change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?;

Expand Down Expand Up @@ -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);

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,
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,14 @@ 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(),
));
}
}
}
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)]

Check warning on line 8 in contracts/interchain-token-service/src/contract/query.rs

View check run for this annotation

Codecov / codecov/patch

contracts/interchain-token-service/src/contract/query.rs#L8

Added line #L8 was not covered by tests
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
Loading