diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index 503ea1539..fb97a22ba 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -29,6 +29,8 @@ pub enum Error { RegisterItsContract, #[error("failed to deregsiter an its edge contract")] DeregisterItsContract, + #[error("failed to set chain config")] + SetChainConfig, #[error("too many coins attached. Execute accepts zero or one coins")] TooManyCoins, #[error("failed to query its address")] @@ -99,6 +101,12 @@ pub fn execute( execute::deregister_its_contract(deps, chain) .change_context(Error::DeregisterItsContract) } + ExecuteMsg::SetChainConfig { + chain, + max_uint, + max_target_decimals, + } => execute::set_chain_config(deps, chain, max_uint, max_target_decimals) + .change_context(Error::DeregisterItsContract), }? .then(Ok) } diff --git a/contracts/interchain-token-service/src/contract/execute.rs b/contracts/interchain-token-service/src/contract/execute.rs index 11f3e0558..3d04b5702 100644 --- a/contracts/interchain-token-service/src/contract/execute.rs +++ b/contracts/interchain-token-service/src/contract/execute.rs @@ -1,6 +1,6 @@ -use axelar_wasm_std::IntoContractError; -use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage}; -use error_stack::{bail, ensure, report, Result, ResultExt}; +use axelar_wasm_std::{FnExt, IntoContractError}; +use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage, Uint256}; +use error_stack::{bail, ensure, report, Report, Result, ResultExt}; use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use crate::events::Event; @@ -27,6 +27,10 @@ pub enum Error { NexusQueryError, #[error("storage error")] StorageError, + #[error("chain config for {0} already set")] + ChainConfigAlreadySet(ChainNameRaw), + #[error("invalid chain max uint")] + InvalidChainMaxUint, } /// Executes an incoming ITS message. @@ -132,3 +136,25 @@ pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result Result { + match state::load_chain_config(deps.storage, &chain).change_context(Error::StorageError)? { + Some(_) => bail!(Error::ChainConfigAlreadySet(chain)), + None => state::save_chain_config( + deps.storage, + &chain, + max_uint + .try_into() + .map_err(Report::new) + .change_context(Error::InvalidChainMaxUint)?, + max_target_decimals, + ) + .change_context(Error::StorageError)? + .then(|_| Ok(Response::new())), + } +} diff --git a/contracts/interchain-token-service/src/msg.rs b/contracts/interchain-token-service/src/msg.rs index c9ff32b55..7dd64d209 100644 --- a/contracts/interchain-token-service/src/msg.rs +++ b/contracts/interchain-token-service/src/msg.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use axelarnet_gateway::AxelarExecutableMsg; use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Uint256; use msgs_derive::EnsurePermissions; use router_api::{Address, ChainNameRaw}; @@ -33,6 +34,13 @@ pub enum ExecuteMsg { /// The admin is allowed to remove the ITS address of a chain for emergencies. #[permission(Elevated)] DeregisterItsContract { chain: ChainNameRaw }, + + #[permission(Governance)] + SetChainConfig { + chain: ChainNameRaw, + max_uint: Uint256, + max_target_decimals: u8, + }, } #[cw_serde] diff --git a/contracts/interchain-token-service/src/state.rs b/contracts/interchain-token-service/src/state.rs index c977ad5fa..e141d8ee5 100644 --- a/contracts/interchain-token-service/src/state.rs +++ b/contracts/interchain-token-service/src/state.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use axelar_wasm_std::{nonempty, IntoContractError}; +use axelar_wasm_std::nonempty::{self, Uint256}; +use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; @@ -25,8 +26,15 @@ pub struct Config { pub axelarnet_gateway: Addr, } +#[cw_serde] +pub struct ChainConfig { + max_uint: Uint256, + max_target_decimals: u8, +} + const CONFIG: Item = Item::new("config"); const ITS_CONTRACTS: Map<&ChainNameRaw, Address> = Map::new("its_contracts"); +const CHAIN_CONFIGS: Map<&ChainNameRaw, ChainConfig> = Map::new("chain_configs"); pub fn load_config(storage: &dyn Storage) -> Config { CONFIG @@ -38,6 +46,29 @@ pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Err Ok(CONFIG.save(storage, config)?) } +pub fn load_chain_config( + storage: &dyn Storage, + chain: &ChainNameRaw, +) -> Result, Error> { + Ok(CHAIN_CONFIGS.may_load(storage, chain)?) +} + +pub fn save_chain_config( + storage: &mut dyn Storage, + chain: &ChainNameRaw, + max_uint: Uint256, + max_target_decimals: u8, +) -> Result<(), Error> { + Ok(CHAIN_CONFIGS.save( + storage, + chain, + &ChainConfig { + max_uint, + max_target_decimals, + }, + )?) +} + pub fn may_load_its_contract( storage: &dyn Storage, chain: &ChainNameRaw, diff --git a/contracts/interchain-token-service/tests/execute.rs b/contracts/interchain-token-service/tests/execute.rs index 368d60d39..4dbd3906e 100644 --- a/contracts/interchain-token-service/tests/execute.rs +++ b/contracts/interchain-token-service/tests/execute.rs @@ -314,3 +314,58 @@ fn execute_message_when_invalid_message_type_fails() { ); assert_err_contains!(result, ExecuteError, ExecuteError::InvalidMessageType); } + +#[test] +fn set_chain_config_should_succeed() { + let chain = "ethereum".parse().unwrap(); + let max_uint = "120000000000000000000000000".parse().unwrap(); + let decimals = 18; + + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + assert_ok!(utils::set_chain_config( + deps.as_mut(), + chain, + max_uint, + decimals + )); +} + +#[test] +fn set_chain_config_should_fail_if_max_uint_is_zero() { + let chain: ChainNameRaw = "ethereum".parse().unwrap(); + let max_uint = 0u64.into(); + let decimals = 18; + + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + assert_err_contains!( + utils::set_chain_config(deps.as_mut(), chain, max_uint, decimals), + ExecuteError, + ExecuteError::InvalidChainMaxUint + ) +} + +#[test] +fn set_chain_config_should_fail_if_chain_config_is_already_set() { + let chain: ChainNameRaw = "ethereum".parse().unwrap(); + let max_uint = "120000000000000000000000000".parse().unwrap(); + let decimals = 18; + + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + assert_ok!(utils::set_chain_config( + deps.as_mut(), + chain.clone(), + max_uint, + decimals + )); + assert_err_contains!( + utils::set_chain_config(deps.as_mut(), chain, max_uint, decimals), + ExecuteError, + ExecuteError::ChainConfigAlreadySet(_) + ) +} diff --git a/contracts/interchain-token-service/tests/utils/execute.rs b/contracts/interchain-token-service/tests/utils/execute.rs index 8dda5fbd5..a16a1bac5 100644 --- a/contracts/interchain-token-service/tests/utils/execute.rs +++ b/contracts/interchain-token-service/tests/utils/execute.rs @@ -7,7 +7,7 @@ use axelar_wasm_std::error::ContractError; use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}; use cosmwasm_std::{ from_json, to_json_binary, Addr, DepsMut, HexBinary, MemoryStorage, OwnedDeps, Response, - WasmQuery, + Uint256, WasmQuery, }; use interchain_token_service::contract; use interchain_token_service::msg::ExecuteMsg; @@ -58,6 +58,24 @@ pub fn deregister_its_contract( ) } +pub fn set_chain_config( + deps: DepsMut, + chain: ChainNameRaw, + max_uint: Uint256, + max_target_decimals: u8, +) -> Result { + contract::execute( + deps, + mock_env(), + mock_info(params::GOVERNANCE, &[]), + ExecuteMsg::SetChainConfig { + chain, + max_uint, + max_target_decimals, + }, + ) +} + pub fn make_deps() -> OwnedDeps> { let addr = Addr::unchecked(params::GATEWAY); let mut deps = OwnedDeps {