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): update chain config #757

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 2 additions & 5 deletions contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,8 @@ pub fn execute(
ExecuteMsg::RegisterChains { chains } => {
execute::register_chains(deps, chains).change_context(Error::RegisterChains)
}
ExecuteMsg::UpdateChain {
chain,
its_edge_contract,
} => {
execute::update_chain(deps, chain, its_edge_contract).change_context(Error::UpdateChain)
ExecuteMsg::UpdateChains { chains } => {
execute::update_chains(deps, chains).change_context(Error::UpdateChain)
}
ExecuteMsg::FreezeChain { chain } => {
freeze_chain(deps, chain).change_context(Error::FreezeChain)
Expand Down
217 changes: 198 additions & 19 deletions contracts/interchain-token-service/src/contract/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ mod interceptors;

#[derive(thiserror::Error, Debug, IntoContractError)]
pub enum Error {
#[error("chain not found {0}")]
ChainNotFound(ChainNameRaw),
#[error("unknown its address {0}")]
UnknownItsContract(Address),
#[error("failed to decode payload")]
Expand Down Expand Up @@ -47,6 +45,8 @@ pub enum Error {
State,
#[error("chain {0} already registered")]
ChainAlreadyRegistered(ChainNameRaw),
#[error("chain {0} not registered")]
ChainNotRegistered(ChainNameRaw),
#[error("token {token_id} not deployed on chain {chain}")]
TokenNotDeployed {
token_id: TokenId,
Expand Down Expand Up @@ -340,7 +340,7 @@ fn send_to_destination(
}

let destination_address = state::load_its_contract(storage, destination_chain)
.change_context_lazy(|| Error::ChainNotFound(destination_chain.clone()))?;
.change_context_lazy(|| Error::ChainNotRegistered(destination_chain.clone()))?;

let config = state::load_config(storage);

Expand Down Expand Up @@ -390,13 +390,21 @@ fn register_chain(storage: &mut dyn Storage, config: msg::ChainConfig) -> Result
}
}

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())
pub fn update_chains(deps: DepsMut, chains: Vec<msg::ChainConfig>) -> Result<Response, Error> {
chains
.into_iter()
.map(|chain_config| update_chain(deps.storage, chain_config))
.try_collect::<_, Vec<Response>, _>()?
.then(|_| Ok(Response::new()))
Comment on lines +394 to +398
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use try_for_each instead. You're not really interested in the returned object unless it's an error

}

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

trait DeploymentType {
Expand All @@ -421,14 +429,14 @@ mod tests {
use axelarnet_gateway::msg::QueryMsg;
use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier};
use cosmwasm_std::{
from_json, to_json_binary, HexBinary, MemoryStorage, OwnedDeps, Uint256, WasmQuery,
from_json, to_json_binary, HexBinary, MemoryStorage, OwnedDeps, Uint128, Uint256, WasmQuery,
};
use router_api::{ChainName, ChainNameRaw, CrossChainId};

use super::apply_to_hub;
use crate::contract::execute::{
disable_execution, enable_execution, execute_message, freeze_chain, register_chain,
register_chains, unfreeze_chain, update_chain, Error,
apply_to_transfer, disable_execution, enable_execution, execute_message, freeze_chain,
register_chain, register_chains, unfreeze_chain, update_chains, Error,
};
use crate::msg::TruncationConfig;
use crate::state::{self, Config};
Expand Down Expand Up @@ -724,19 +732,190 @@ mod tests {
}

#[test]
fn update_chain_fails_if_not_registered() {
fn update_chains_fails_if_not_registered() {
let mut deps = mock_dependencies();
assert_err_contains!(
update_chain(
update_chains(
deps.as_mut(),
SOLANA.parse().unwrap(),
ITS_ADDRESS.parse().unwrap()
vec![msg::ChainConfig {
chain: SOLANA.parse().unwrap(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
truncation: TruncationConfig {
max_uint: Uint256::MAX.try_into().unwrap(),
max_decimals_when_truncating: 16u8,
},
}]
),
Error,
Error::State
Error::ChainNotRegistered(..)
);
}

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

let token_id: TokenId = [7u8; 32].into();
let solana = ChainNameRaw::try_from(SOLANA).unwrap();
let ethereum = ChainNameRaw::try_from(ETHEREUM).unwrap();

// update the max_uint to u128 max (previously was u256 max) and reduce decimals when truncating to 6
let new_decimals = 6u8;
assert_ok!(update_chains(
deps.as_mut(),
vec![msg::ChainConfig {
chain: solana.clone(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
truncation: TruncationConfig {
max_uint: Uint128::MAX.try_into().unwrap(),
max_decimals_when_truncating: new_decimals,
},
}]
));

// now deploy a token with 18 decimals. Should truncate to 6
let msg = HubMessage::SendToHub {
destination_chain: solana.clone(),
message: DeployInterchainToken {
token_id,
name: "Test".parse().unwrap(),
symbol: "TEST".parse().unwrap(),
decimals: 18,
minter: None,
}
.into(),
};
let cc_id = CrossChainId {
source_chain: ethereum.clone(),
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32).into(),
};

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

// destination token instance should use 6 decimals
let destination_token_instance = assert_ok!(state::may_load_token_instance(
deps.as_mut().storage,
solana.clone(),
token_id,
));
assert!(destination_token_instance.is_some());
assert_eq!(destination_token_instance.unwrap().decimals, new_decimals);

// source instance should use 18
let source_token_instance = assert_ok!(state::may_load_token_instance(
deps.as_mut().storage,
ethereum.clone(),
token_id,
));
assert!(source_token_instance.is_some());
assert_eq!(source_token_instance.unwrap().decimals, 18u8);

// transfers should be scaled appropriately
let transfer = InterchainTransfer {
token_id,
amount: Uint256::from_u128(1000000000000).try_into().unwrap(),
source_address: its_address(),
destination_address: its_address(),
data: None,
};
let transformed_transfer = assert_ok!(apply_to_transfer(
deps.as_mut().storage,
ethereum,
solana,
transfer.clone(),
));
assert_eq!(
transformed_transfer.amount,
Uint256::one().try_into().unwrap()
);
}

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

let token_id: TokenId = [7u8; 32].into();
let solana = ChainNameRaw::try_from(SOLANA).unwrap();
let ethereum = ChainNameRaw::try_from(ETHEREUM).unwrap();

// deploy a token with 18 decimals
let msg = HubMessage::SendToHub {
destination_chain: solana.clone(),
message: DeployInterchainToken {
token_id,
name: "Test".parse().unwrap(),
symbol: "TEST".parse().unwrap(),
decimals: 18,
minter: None,
}
.into(),
};
let cc_id = CrossChainId {
source_chain: ethereum.clone(),
message_id: HexTxHashAndEventIndex::new([1u8; 32], 0u32).into(),
};

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

// update the max_uint to u128 max (previously was u256 max) and reduce decimals when truncating to 6
assert_ok!(update_chains(
deps.as_mut(),
vec![msg::ChainConfig {
chain: solana.clone(),
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
truncation: TruncationConfig {
max_uint: Uint128::MAX.try_into().unwrap(),
max_decimals_when_truncating: 6u8,
},
}]
));

// previously deployed tokens should have 18 decimals, unaffected by the config update
let destination_token_instance = assert_ok!(state::may_load_token_instance(
deps.as_mut().storage,
solana.clone(),
token_id
));
assert!(destination_token_instance.is_some());
assert_eq!(destination_token_instance.unwrap().decimals, 18u8);

let source_token_instance = assert_ok!(state::may_load_token_instance(
deps.as_mut().storage,
ethereum.clone(),
token_id
));
assert!(source_token_instance.is_some());
assert_eq!(source_token_instance.unwrap().decimals, 18u8);

// transfers should not be scaled, since decimals are the same
let transfer = InterchainTransfer {
token_id,
amount: Uint256::from_u128(1000000000000).try_into().unwrap(),
source_address: its_address(),
destination_address: its_address(),
data: None,
};
let transformed_transfer = assert_ok!(apply_to_transfer(
deps.as_mut().storage,
ethereum,
solana,
transfer.clone(),
));
assert_eq!(transformed_transfer.amount, transfer.amount);
}

#[test]
fn should_link_custom_tokens_with_different_decimals() {
let mut deps = mock_dependencies();
Expand Down Expand Up @@ -1131,7 +1310,7 @@ mod tests {
its_edge_contract: ITS_ADDRESS.to_string().try_into().unwrap(),
truncation: TruncationConfig {
max_uint: Uint256::MAX.try_into().unwrap(),
max_decimals_when_truncating: 16u8
max_decimals_when_truncating: 18u8
}
}
));
Expand Down
10 changes: 4 additions & 6 deletions contracts/interchain-token-service/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,14 @@ pub enum ExecuteMsg {
/// 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.
/// If any chain is already registered, an error is returned.
#[permission(Governance)]
RegisterChains { chains: Vec<ChainConfig> },

/// Update the address of the ITS contract registered to the specified chain
/// For each chain, update the ITS contract and config parameters.
/// If any chain has not been registered, returns an error
#[permission(Governance)]
UpdateChain {
chain: ChainNameRaw,
its_edge_contract: Address,
},
UpdateChains { chains: Vec<ChainConfig> },

/// Freeze execution of ITS messages for a particular chain
#[permission(Elevated)]
Expand Down
16 changes: 0 additions & 16 deletions contracts/interchain-token-service/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,22 +180,6 @@ pub fn save_chain_config(
.change_context(Error::Storage)
}

pub fn update_its_contract(
storage: &mut dyn Storage,
chain: &ChainNameRaw,
its_address: Address,
) -> Result<ChainConfig, Error> {
CHAIN_CONFIGS
.update(storage, chain, |config| match config {
Some(config) => Ok(ChainConfig {
its_address,
..config
}),
None => Err(StdError::not_found("config not found")),
})
.change_context(Error::ChainNotFound(chain.to_owned()))
}

pub fn may_load_its_contract(
storage: &dyn Storage,
chain: &ChainNameRaw,
Expand Down
10 changes: 7 additions & 3 deletions contracts/interchain-token-service/tests/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use axelar_wasm_std::response::inspect_response_msg;
use axelar_wasm_std::{assert_err_contains, nonempty, permission_control};
use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg;
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
use cosmwasm_std::{HexBinary, Uint256};
use cosmwasm_std::{HexBinary, Uint128, Uint256};
use interchain_token_service::contract::{self, ExecuteError};
use interchain_token_service::events::Event;
use interchain_token_service::msg::{self, ExecuteMsg, TruncationConfig};
Expand Down Expand Up @@ -48,7 +48,9 @@ fn register_update_its_contract_succeeds() {
assert_ok!(utils::update_chain(
deps.as_mut(),
chain.clone(),
new_address.clone()
new_address.clone(),
Uint128::MAX.try_into().unwrap(),
18u8
));
let res = assert_ok!(utils::query_its_chain(deps.as_ref(), chain.clone()));
assert_eq!(res.unwrap().its_edge_contract, new_address);
Expand Down Expand Up @@ -98,7 +100,9 @@ fn update_unknown_chain_fails() {
chain,
"0x1234567890123456789012345678901234567890"
.parse()
.unwrap()
.unwrap(),
Uint256::MAX.try_into().unwrap(),
u8::MAX
),
Error,
Error::UpdateChain
Expand Down
6 changes: 4 additions & 2 deletions contracts/interchain-token-service/tests/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use assert_ok::assert_ok;
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::Uint256;
use cosmwasm_std::{Uint128, Uint256};
use interchain_token_service::msg::{ChainConfigResponse, TruncationConfig};
use interchain_token_service::TokenId;
use router_api::{Address, ChainNameRaw};
Expand Down Expand Up @@ -54,7 +54,9 @@ fn query_chain_config() {
assert_ok!(utils::update_chain(
deps.as_mut(),
chain.clone(),
new_address.clone()
new_address.clone(),
Uint128::MAX.try_into().unwrap(),
18,
));

let chain_config = assert_ok!(utils::query_its_chain(deps.as_ref(), chain.clone()));
Expand Down
Loading
Loading