Skip to content

feat(minor-interchain-token-service): refactor chain registration #681

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

Merged
merged 7 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
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
151 changes: 110 additions & 41 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,48 @@ 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,
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.chain,
chain_config.its_edge_contract,
chain_config.max_uint,
chain_config.max_target_decimals,
)
})
.try_collect::<_, Vec<Response>, _>()?
.then(|_| Ok(Response::new()))
}

pub fn register_chain(
storage: &mut dyn Storage,
chain: ChainNameRaw,
its_address: Address,
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)
.change_context(Error::State)?
.then(|_| Ok(Response::new())),
match state::may_load_chain_config(storage, &chain).change_context(Error::State)? {
Some(_) => bail!(Error::ChainAlreadyRegistered(chain)),
None => {
state::save_chain_config(storage, &chain, its_address, max_uint, max_target_decimals)
.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 +623,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 +854,68 @@ mod tests {
));
}

#[test]
fn register_chain_fails_if_already_registered() {
let mut deps = mock_dependencies();
assert_ok!(register_chain(
deps.as_mut().storage,
SOLANA.parse().unwrap(),
ITS_ADDRESS.to_string().try_into().unwrap(),
Uint256::one().try_into().unwrap(),
16u8
));
assert_err_contains!(
register_chain(
deps.as_mut().storage,
SOLANA.parse().unwrap(),
ITS_ADDRESS.to_string().try_into().unwrap(),
Uint256::one().try_into().unwrap(),
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,14 +940,10 @@ 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(),
assert_ok!(register_chain(
deps.as_mut().storage,
chain.clone(),
ITS_ADDRESS.to_string().try_into().unwrap(),
));
assert_ok!(set_chain_config(
deps.as_mut(),
chain,
Uint256::one().try_into().unwrap(),
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 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 @@
/// 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 @@

#[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]

Check warning on line 56 in contracts/interchain-token-service/src/msg.rs

View check run for this annotation

Codecov / codecov/patch

contracts/interchain-token-service/src/msg.rs#L56

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