Skip to content

Commit

Permalink
feat(minor-interchain-token-service): refactor chain registration (#681)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcobb23 authored Nov 7, 2024
1 parent 98acb3b commit 08bec3c
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 291 deletions.
38 changes: 12 additions & 26 deletions contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Respons
use error_stack::{Report, ResultExt};
use execute::{freeze_chain, unfreeze_chain};

use crate::events::Event;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state;
use crate::state::Config;
Expand All @@ -26,10 +25,10 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub enum Error {
#[error("failed to execute a cross-chain message")]
Execute,
#[error("failed to register an its edge contract")]
RegisterItsContract,
#[error("failed to deregsiter an its edge contract")]
DeregisterItsContract,
#[error("failed to register chains")]
RegisterChains,
#[error("failed to update chain")]
UpdateChain,
#[error("failed to freeze chain")]
FreezeChain,
#[error("failed to unfreeze chain")]
Expand Down Expand Up @@ -79,17 +78,9 @@ pub fn instantiate(

state::save_config(deps.storage, &Config { axelarnet_gateway })?;

for (chain, address) in msg.its_contracts.iter() {
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()
.map(|(chain, address)| Event::ItsContractRegistered { chain, address }.into()),
))
Ok(Response::new())
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -106,13 +97,14 @@ pub fn execute(
payload,
}) => execute::execute_message(deps, cc_id, source_address, payload)
.change_context(Error::Execute),
ExecuteMsg::RegisterItsContract { chain, address } => {
execute::register_its_contract(deps, chain, address)
.change_context(Error::RegisterItsContract)
ExecuteMsg::RegisterChains { chains } => {
execute::register_chains(deps, chains).change_context(Error::RegisterChains)
}
ExecuteMsg::DeregisterItsContract { chain } => {
execute::deregister_its_contract(deps, chain)
.change_context(Error::DeregisterItsContract)
ExecuteMsg::UpdateChain {
chain,
its_edge_contract,
} => {
execute::update_chain(deps, chain, its_edge_contract).change_context(Error::UpdateChain)
}
ExecuteMsg::FreezeChain { chain } => {
freeze_chain(deps, chain).change_context(Error::FreezeChain)
Expand All @@ -126,12 +118,6 @@ pub fn execute(
ExecuteMsg::EnableExecution => {
execute::enable_execution(deps).change_context(Error::EnableExecution)
}
ExecuteMsg::SetChainConfig {
chain,
max_uint,
max_target_decimals,
} => execute::set_chain_config(deps, chain, max_uint, max_target_decimals)
.change_context(Error::SetChainConfig),
}?
.then(Ok)
}
Expand Down
153 changes: 106 additions & 47 deletions contracts/interchain-token-service/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use axelar_wasm_std::{killswitch, nonempty, FnExt, IntoContractError};
use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage, Uint256};
use error_stack::{bail, ensure, report, Result, ResultExt};
use itertools::Itertools;
use router_api::{Address, ChainNameRaw, CrossChainId};

use crate::events::Event;
use crate::primitives::HubMessage;
use crate::state::{self, is_chain_frozen, load_config, load_its_contract, TokenDeploymentType};
use crate::{
DeployInterchainToken, InterchainTransfer, Message, TokenConfig, TokenId, TokenInstance,
msg, DeployInterchainToken, InterchainTransfer, Message, TokenConfig, TokenId, TokenInstance,
};

#[derive(thiserror::Error, Debug, IntoContractError)]
pub enum Error {
#[error("unknown chain {0}")]
UnknownChain(ChainNameRaw),
#[error("chain not found {0}")]
ChainNotFound(ChainNameRaw),
#[error("unknown its address {0}")]
UnknownItsContract(Address),
#[error("failed to decode payload")]
Expand All @@ -40,8 +41,8 @@ pub enum Error {
},
#[error("state error")]
State,
#[error("chain config for {0} already set")]
ChainConfigAlreadySet(ChainNameRaw),
#[error("chain {0} already registered")]
ChainAlreadyRegistered(ChainNameRaw),
#[error("token {token_id} not deployed on chain {chain}")]
TokenNotDeployed {
token_id: TokenId,
Expand Down Expand Up @@ -107,7 +108,7 @@ fn execute_message_on_hub(
message: Message,
) -> Result<Response, Error> {
let destination_address = load_its_contract(deps.storage, &destination_chain)
.change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?;
.change_context_lazy(|| Error::ChainNotFound(destination_chain.clone()))?;

let message = apply_to_hub(
deps.storage,
Expand Down Expand Up @@ -177,7 +178,7 @@ fn ensure_is_its_source_address(
source_address: &Address,
) -> Result<(), Error> {
let source_its_contract = load_its_contract(storage, source_chain)
.change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?;
.change_context_lazy(|| Error::ChainNotFound(source_chain.clone()))?;

ensure!(
source_address == &source_its_contract,
Expand Down Expand Up @@ -205,24 +206,6 @@ fn send_to_destination(
Ok(Response::new().add_message(call_contract_msg))
}

pub fn register_its_contract(
deps: DepsMut,
chain: ChainNameRaw,
address: Address,
) -> Result<Response, Error> {
state::save_its_contract(deps.storage, &chain, &address)
.change_context_lazy(|| Error::FailedItsContractRegistration(chain.clone()))?;

Ok(Response::new().add_event(Event::ItsContractRegistered { chain, address }.into()))
}

pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result<Response, Error> {
state::remove_its_contract(deps.storage, &chain)
.change_context_lazy(|| Error::FailedItsContractDeregistration(chain.clone()))?;

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)?;

Expand All @@ -243,20 +226,32 @@ 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,
max_uint: nonempty::Uint256,
max_target_decimals: u8,
) -> Result<Response, Error> {
match state::may_load_chain_config(deps.storage, &chain).change_context(Error::State)? {
Some(_) => bail!(Error::ChainConfigAlreadySet(chain)),
None => state::save_chain_config(deps.storage, &chain, max_uint, max_target_decimals)
pub fn register_chains(deps: DepsMut, chains: Vec<msg::ChainConfig>) -> Result<Response, Error> {
chains
.into_iter()
.map(|chain_config| register_chain(deps.storage, chain_config))
.try_collect::<_, Vec<Response>, _>()?
.then(|_| Ok(Response::new()))
}

fn register_chain(storage: &mut dyn Storage, config: msg::ChainConfig) -> Result<Response, Error> {
match state::may_load_chain_config(storage, &config.chain).change_context(Error::State)? {
Some(_) => bail!(Error::ChainAlreadyRegistered(config.chain)),
None => state::save_chain_config(storage, &config.chain.clone(), config)
.change_context(Error::State)?
.then(|_| Ok(Response::new())),
}
}

pub fn update_chain(
deps: DepsMut,
chain: ChainNameRaw,
its_address: Address,
) -> Result<Response, Error> {
state::update_its_contract(deps.storage, &chain, its_address).change_context(Error::State)?;
Ok(Response::new())
}

/// Calculates the destination on token transfer amount.
///
/// The amount is calculated based on the token decimals on the source and destination chains.
Expand Down Expand Up @@ -612,11 +607,11 @@ mod tests {
use router_api::{ChainNameRaw, CrossChainId};

use crate::contract::execute::{
disable_execution, enable_execution, execute_message, freeze_chain, register_its_contract,
set_chain_config, unfreeze_chain, Error,
disable_execution, enable_execution, execute_message, freeze_chain, register_chain,
register_chains, unfreeze_chain, update_chain, Error,
};
use crate::state::{self, Config};
use crate::{DeployInterchainToken, HubMessage, InterchainTransfer};
use crate::{msg, DeployInterchainToken, HubMessage, InterchainTransfer};

const SOLANA: &str = "solana";
const ETHEREUM: &str = "ethereum";
Expand Down Expand Up @@ -843,6 +838,72 @@ mod tests {
));
}

#[test]
fn register_chain_fails_if_already_registered() {
let mut deps = mock_dependencies();
assert_ok!(register_chain(
deps.as_mut().storage,
msg::ChainConfig {
chain: SOLANA.parse().unwrap(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
max_uint: Uint256::one().try_into().unwrap(),
max_target_decimals: 16u8
}
));
assert_err_contains!(
register_chain(
deps.as_mut().storage,
msg::ChainConfig {
chain: SOLANA.parse().unwrap(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
max_uint: Uint256::one().try_into().unwrap(),
max_target_decimals: 16u8
}
),
Error,
Error::ChainAlreadyRegistered(..)
);
}

#[test]
fn register_chains_fails_if_any_already_registered() {
let mut deps = mock_dependencies();
let chains = vec![
msg::ChainConfig {
chain: SOLANA.parse().unwrap(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
max_uint: Uint256::MAX.try_into().unwrap(),
max_target_decimals: 16u8,
},
msg::ChainConfig {
chain: XRPL.parse().unwrap(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
max_uint: Uint256::MAX.try_into().unwrap(),
max_target_decimals: 16u8,
},
];
assert_ok!(register_chains(deps.as_mut(), chains[0..1].to_vec()));
assert_err_contains!(
register_chains(deps.as_mut(), chains,),
Error,
Error::ChainAlreadyRegistered(..)
);
}

#[test]
fn update_chain_fails_if_not_registered() {
let mut deps = mock_dependencies();
assert_err_contains!(
update_chain(
deps.as_mut(),
SOLANA.parse().unwrap(),
ITS_ADDRESS.parse().unwrap()
),
Error,
Error::State
);
}

fn init(deps: &mut OwnedDeps<MemoryStorage, MockApi, MockQuerier>) {
assert_ok!(permission_control::set_admin(
deps.as_mut().storage,
Expand All @@ -867,16 +928,14 @@ mod tests {

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
assert_ok!(register_chain(
deps.as_mut().storage,
msg::ChainConfig {
chain: chain.clone(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
max_uint: Uint256::one().try_into().unwrap(),
max_target_decimals: 16u8
}
));
}
}
Expand Down
33 changes: 17 additions & 16 deletions contracts/interchain-token-service/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ pub struct InstantiateMsg {
pub admin_address: String,
/// The address of the axelarnet-gateway contract on Amplifier
pub axelarnet_gateway_address: String,
/// Addresses of the ITS edge contracts on connected chains
pub its_contracts: HashMap<ChainNameRaw, Address>,
}

#[cw_serde]
Expand All @@ -25,18 +23,20 @@ pub enum ExecuteMsg {
/// Execute a cross-chain message received by the axelarnet-gateway from another chain
#[permission(Specific(gateway))]
Execute(AxelarExecutableMsg),
/// Register the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before

/// For each chain, register the ITS contract and set config parameters.
/// Each chain's ITS contract has to be whitelisted before
/// ITS Hub can send cross-chain messages to it, or receive messages from it.
/// If an ITS contract is already set for the chain, an error is returned.
#[permission(Governance)]
RegisterItsContract {
RegisterChains { chains: Vec<ChainConfig> },

/// Update the address of the ITS contract registered to the specified chain
#[permission(Governance)]
UpdateChain {
chain: ChainNameRaw,
address: Address,
its_edge_contract: Address,
},
/// Deregister the ITS contract address for the given chain.
/// The admin is allowed to remove the ITS address of a chain for emergencies.
#[permission(Elevated)]
DeregisterItsContract { chain: ChainNameRaw },

/// Freeze execution of ITS messages for a particular chain
#[permission(Elevated)]
Expand All @@ -51,13 +51,14 @@ pub enum ExecuteMsg {

#[permission(Elevated)]
EnableExecution,
/// Set the chain configuration for a chain.
#[permission(Governance)]
SetChainConfig {
chain: ChainNameRaw,
max_uint: nonempty::Uint256, // The maximum uint value that is supported by the chain's token standard
max_target_decimals: u8, // The maximum number of decimals that is preserved when deploying a token to another chain where smaller uint values are used
},
}

#[cw_serde]
pub struct ChainConfig {
pub chain: ChainNameRaw,
pub its_edge_contract: Address,
pub max_uint: nonempty::Uint256, // The maximum uint value that is supported by the chain's token standard
pub max_target_decimals: u8, // The maximum number of decimals that is preserved when deploying a token to another chain where smaller uint values are used
}

#[cw_serde]
Expand Down
Loading

0 comments on commit 08bec3c

Please sign in to comment.