Skip to content

Commit

Permalink
feat(minor-interchain-token-service): register gateway token (axelarn…
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcobb23 authored Oct 3, 2024
1 parent a74a60a commit c8c27d4
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 10 deletions.
88 changes: 88 additions & 0 deletions contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ pub enum Error {
RegisterItsContract,
#[error("failed to deregsiter an its edge contract")]
DeregisterItsContract,
#[error("failed to register gateway token")]
RegisterGatewayToken,
#[error("failed to query its address")]
QueryItsContract,
#[error("failed to query all its addresses")]
QueryAllItsContracts,
#[error("failed to query gateway tokens")]
QueryGatewayTokens,
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down Expand Up @@ -97,6 +101,11 @@ pub fn execute(
execute::deregister_its_contract(deps, chain)
.change_context(Error::DeregisterItsContract)
}
ExecuteMsg::RegisterGatewayToken {
denom,
source_chain,
} => execute::register_gateway_token(deps, denom, source_chain)
.change_context(Error::RegisterGatewayToken),
}?
.then(Ok)
}
Expand All @@ -114,6 +123,85 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, ContractError>
QueryMsg::AllItsContracts => {
query::all_its_contracts(deps).change_context(Error::QueryAllItsContracts)
}
QueryMsg::GatewayTokens => {
query::gateway_tokens(deps).change_context(Error::QueryGatewayTokens)
}
}?
.then(Ok)
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use axelar_wasm_std::nonempty;
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage,
};
use cosmwasm_std::{from_json, to_json_binary, OwnedDeps, WasmQuery};
use router_api::{ChainName, ChainNameRaw};

use super::{execute, instantiate};
use crate::contract::execute::gateway_token_id;
use crate::contract::query;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::TokenId;
const GOVERNANCE_ADDRESS: &str = "governance";
const ADMIN_ADDRESS: &str = "admin";
const AXELARNET_GATEWAY_ADDRESS: &str = "axelarnet-gateway";

#[test]
fn register_gateway_token_should_register_denom_and_token_id() {
let mut deps = setup();
let denom = "uaxl";
let res = execute(
deps.as_mut(),
mock_env(),
mock_info(GOVERNANCE_ADDRESS, &[]),
ExecuteMsg::RegisterGatewayToken {
denom: denom.try_into().unwrap(),
source_chain: ChainNameRaw::try_from("axelar").unwrap(),
},
);
assert!(res.is_ok());

let tokens: HashMap<nonempty::String, TokenId> =
from_json(query(deps.as_ref(), mock_env(), QueryMsg::GatewayTokens).unwrap()).unwrap();
assert_eq!(tokens.len(), 1);
assert_eq!(
tokens,
HashMap::from([(
denom.try_into().unwrap(),
gateway_token_id(&deps.as_mut(), denom).unwrap()
)])
);
}

fn setup() -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();

instantiate(
deps.as_mut(),
mock_env(),
mock_info("instantiator", &[]),
InstantiateMsg {
governance_address: GOVERNANCE_ADDRESS.to_string(),
admin_address: ADMIN_ADDRESS.to_string(),
axelarnet_gateway_address: AXELARNET_GATEWAY_ADDRESS.to_string(),
its_contracts: HashMap::new(),
},
)
.unwrap();

deps.querier.update_wasm(move |wq| match wq {
WasmQuery::Smart { contract_addr, .. }
if contract_addr == AXELARNET_GATEWAY_ADDRESS =>
{
Ok(to_json_binary(&ChainName::try_from("axelar").unwrap()).into()).into()
}
_ => panic!("no mock for this query"),
});

deps
}
}
127 changes: 126 additions & 1 deletion contracts/interchain-token-service/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
use axelar_wasm_std::IntoContractError;
use axelar_wasm_std::{nonempty, FnExt, IntoContractError};
use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage};
use error_stack::{bail, ensure, report, Result, ResultExt};
use router_api::{Address, ChainName, ChainNameRaw, CrossChainId};
use sha3::{Digest, Keccak256};

use crate::events::Event;
use crate::primitives::HubMessage;
use crate::state::{self, load_config, load_its_contract};
use crate::TokenId;

// this is just keccak256("its-interchain-token-id-gateway")
const GATEWAY_TOKEN_PREFIX: [u8; 32] = [
106, 80, 188, 250, 12, 170, 167, 223, 94, 185, 52, 185, 146, 147, 21, 23, 145, 36, 97, 146,
215, 72, 32, 167, 6, 16, 83, 155, 176, 213, 112, 44,
];

#[derive(thiserror::Error, Debug, IntoContractError)]
pub enum Error {
Expand All @@ -21,6 +29,10 @@ pub enum Error {
FailedItsContractRegistration(ChainNameRaw),
#[error("failed to deregister its contract for chain {0}")]
FailedItsContractDeregistration(ChainNameRaw),
#[error("failed to register gateway token")]
FailedGatewayTokenRegistration,
#[error("failed to generate token id")]
FailedTokenIdGeneration,
}

/// Executes an incoming ITS message.
Expand Down Expand Up @@ -126,3 +138,116 @@ pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result<Res

Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into()))
}

pub fn register_gateway_token(
deps: DepsMut,
denom: nonempty::String,
_chain: ChainNameRaw,
) -> Result<Response, Error> {
let token_id = gateway_token_id(&deps, &denom)?;
state::save_gateway_token_denom(deps.storage, token_id, denom)
.change_context(Error::FailedGatewayTokenRegistration)?;
Ok(Response::new())
}

pub fn gateway_token_id(deps: &DepsMut, denom: &str) -> Result<TokenId, Error> {
let config = state::load_config(deps.storage);
let gateway: axelarnet_gateway::Client =
client::ContractClient::new(deps.querier, &config.axelarnet_gateway).into();
let chain_name = gateway
.chain_name()
.change_context(Error::FailedTokenIdGeneration)?;
let chain_name_hash: [u8; 32] = Keccak256::digest(chain_name.to_string().as_bytes()).into();

Keccak256::digest([&GATEWAY_TOKEN_PREFIX, &chain_name_hash, denom.as_bytes()].concat())
.then(<[u8; 32]>::from)
.then(TokenId::new)
.then(Ok)
}

#[cfg(test)]
mod tests {
use assert_ok::assert_ok;
use axelar_wasm_std::assert_err_contains;
use axelarnet_gateway::msg::QueryMsg;
use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier};
use cosmwasm_std::{from_json, to_json_binary, Addr, MemoryStorage, OwnedDeps, WasmQuery};
use router_api::{ChainName, ChainNameRaw};

use super::{gateway_token_id, register_gateway_token, Error};
use crate::state::{self, Config};

#[test]
fn gateway_token_id_should_be_idempotent() {
let mut deps = init();
let denom = "uaxl";
let token_id = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
let token_id_2 = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
assert_eq!(token_id, token_id_2);
}

#[test]
fn gateway_token_id_should_differ_for_different_denoms() {
let mut deps = init();
let axl_denom = "uaxl";
let eth_denom = "eth";
let token_id_axl = assert_ok!(gateway_token_id(&deps.as_mut(), axl_denom));
let token_id_eth = assert_ok!(gateway_token_id(&deps.as_mut(), eth_denom));
assert_ne!(token_id_axl, token_id_eth);
}

#[test]
fn gateway_token_id_should_not_change() {
let mut deps = init();
let denom = "uaxl";
let token_id = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
goldie::assert_json!(token_id);
}

#[test]
fn register_token_id_should_not_overwrite() {
let mut deps = init();
let denom = "uaxl";
let chain = ChainNameRaw::try_from("ethereum").unwrap();
assert_ok!(register_gateway_token(
deps.as_mut(),
denom.try_into().unwrap(),
chain.clone()
));
// calling again should fail
assert_err_contains!(
register_gateway_token(deps.as_mut(), denom.try_into().unwrap(), chain),
Error,
Error::FailedGatewayTokenRegistration
);
}

fn init() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let addr = Addr::unchecked("axelar-gateway");
let mut deps = mock_dependencies();
state::save_config(
deps.as_mut().storage,
&Config {
axelarnet_gateway: addr.clone(),
},
)
.unwrap();

let mut querier = MockQuerier::default();
querier.update_wasm(move |msg| match msg {
WasmQuery::Smart { contract_addr, msg } if contract_addr == &addr.to_string() => {
let msg = from_json::<QueryMsg>(msg).unwrap();
match msg {
QueryMsg::ChainName {} => {
Ok(to_json_binary(&ChainName::try_from("axelar").unwrap()).into()).into()
}
_ => panic!("unsupported query"),
}
}
_ => panic!("unexpected query: {:?}", msg),
});

deps.querier = querier;
deps
}
}
5 changes: 5 additions & 0 deletions contracts/interchain-token-service/src/contract/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ 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 gateway_tokens(deps: Deps) -> Result<Binary, state::Error> {
let tokens = state::load_all_gateway_tokens(deps.storage)?;
Ok(to_json_binary(&tokens)?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"12b0c9bff75ed8db58228ceff639071ca700a2b608f4f086f18f8208fbe9c3e0"
12 changes: 12 additions & 0 deletions contracts/interchain-token-service/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::collections::HashMap;

use axelar_wasm_std::nonempty;
use axelarnet_gateway::AxelarExecutableMsg;
use cosmwasm_schema::{cw_serde, QueryResponses};
use msgs_derive::EnsurePermissions;
use router_api::{Address, ChainNameRaw};

use crate::TokenId;

#[cw_serde]
pub struct InstantiateMsg {
pub governance_address: String,
Expand Down Expand Up @@ -33,6 +36,13 @@ pub enum ExecuteMsg {
/// The admin is allowed to remove the ITS address of a chain for emergencies.
#[permission(Elevated)]
DeregisterItsContract { chain: ChainNameRaw },

/// Register legacy gateway token with ITS
#[permission(Governance)]
RegisterGatewayToken {
denom: nonempty::String,
source_chain: ChainNameRaw,
},
}

#[cw_serde]
Expand All @@ -44,4 +54,6 @@ pub enum QueryMsg {
/// Query all registererd ITS contract addresses
#[returns(HashMap<ChainNameRaw, Address>)]
AllItsContracts,
#[returns(HashMap<nonempty::String, TokenId>)]
GatewayTokens,
}
22 changes: 21 additions & 1 deletion contracts/interchain-token-service/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use std::fmt::Display;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, Uint256};
use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey};
use router_api::ChainNameRaw;
use strum::FromRepr;

/// A unique 32-byte identifier for linked cross-chain tokens across ITS contracts.
#[cw_serde]
#[derive(Eq)]
#[derive(Eq, Hash)]
pub struct TokenId(
#[serde(with = "axelar_wasm_std::hex")]
#[schemars(with = "String")]
Expand Down Expand Up @@ -124,3 +125,22 @@ impl From<TokenId> for [u8; 32] {
id.0
}
}

impl<'a> PrimaryKey<'a> for TokenId {
type Prefix = ();
type SubPrefix = ();
type Suffix = Self;
type SuperSuffix = Self;

fn key(&self) -> Vec<Key> {
self.0.key()
}
}

impl KeyDeserialize for TokenId {
type Output = TokenId;
fn from_vec(value: Vec<u8>) -> cosmwasm_std::StdResult<Self::Output> {
let inner = <[u8; 32]>::from_vec(value)?;
Ok(TokenId(inner))
}
}
Loading

0 comments on commit c8c27d4

Please sign in to comment.