Skip to content

Commit c8c27d4

Browse files
authored
feat(minor-interchain-token-service): register gateway token (#644)
1 parent a74a60a commit c8c27d4

File tree

9 files changed

+294
-10
lines changed

9 files changed

+294
-10
lines changed

contracts/interchain-token-service/src/contract.rs

+88
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ pub enum Error {
2929
RegisterItsContract,
3030
#[error("failed to deregsiter an its edge contract")]
3131
DeregisterItsContract,
32+
#[error("failed to register gateway token")]
33+
RegisterGatewayToken,
3234
#[error("failed to query its address")]
3335
QueryItsContract,
3436
#[error("failed to query all its addresses")]
3537
QueryAllItsContracts,
38+
#[error("failed to query gateway tokens")]
39+
QueryGatewayTokens,
3640
}
3741

3842
#[cfg_attr(not(feature = "library"), entry_point)]
@@ -97,6 +101,11 @@ pub fn execute(
97101
execute::deregister_its_contract(deps, chain)
98102
.change_context(Error::DeregisterItsContract)
99103
}
104+
ExecuteMsg::RegisterGatewayToken {
105+
denom,
106+
source_chain,
107+
} => execute::register_gateway_token(deps, denom, source_chain)
108+
.change_context(Error::RegisterGatewayToken),
100109
}?
101110
.then(Ok)
102111
}
@@ -114,6 +123,85 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, ContractError>
114123
QueryMsg::AllItsContracts => {
115124
query::all_its_contracts(deps).change_context(Error::QueryAllItsContracts)
116125
}
126+
QueryMsg::GatewayTokens => {
127+
query::gateway_tokens(deps).change_context(Error::QueryGatewayTokens)
128+
}
117129
}?
118130
.then(Ok)
119131
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use std::collections::HashMap;
136+
137+
use axelar_wasm_std::nonempty;
138+
use cosmwasm_std::testing::{
139+
mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage,
140+
};
141+
use cosmwasm_std::{from_json, to_json_binary, OwnedDeps, WasmQuery};
142+
use router_api::{ChainName, ChainNameRaw};
143+
144+
use super::{execute, instantiate};
145+
use crate::contract::execute::gateway_token_id;
146+
use crate::contract::query;
147+
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
148+
use crate::TokenId;
149+
const GOVERNANCE_ADDRESS: &str = "governance";
150+
const ADMIN_ADDRESS: &str = "admin";
151+
const AXELARNET_GATEWAY_ADDRESS: &str = "axelarnet-gateway";
152+
153+
#[test]
154+
fn register_gateway_token_should_register_denom_and_token_id() {
155+
let mut deps = setup();
156+
let denom = "uaxl";
157+
let res = execute(
158+
deps.as_mut(),
159+
mock_env(),
160+
mock_info(GOVERNANCE_ADDRESS, &[]),
161+
ExecuteMsg::RegisterGatewayToken {
162+
denom: denom.try_into().unwrap(),
163+
source_chain: ChainNameRaw::try_from("axelar").unwrap(),
164+
},
165+
);
166+
assert!(res.is_ok());
167+
168+
let tokens: HashMap<nonempty::String, TokenId> =
169+
from_json(query(deps.as_ref(), mock_env(), QueryMsg::GatewayTokens).unwrap()).unwrap();
170+
assert_eq!(tokens.len(), 1);
171+
assert_eq!(
172+
tokens,
173+
HashMap::from([(
174+
denom.try_into().unwrap(),
175+
gateway_token_id(&deps.as_mut(), denom).unwrap()
176+
)])
177+
);
178+
}
179+
180+
fn setup() -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
181+
let mut deps = mock_dependencies();
182+
183+
instantiate(
184+
deps.as_mut(),
185+
mock_env(),
186+
mock_info("instantiator", &[]),
187+
InstantiateMsg {
188+
governance_address: GOVERNANCE_ADDRESS.to_string(),
189+
admin_address: ADMIN_ADDRESS.to_string(),
190+
axelarnet_gateway_address: AXELARNET_GATEWAY_ADDRESS.to_string(),
191+
its_contracts: HashMap::new(),
192+
},
193+
)
194+
.unwrap();
195+
196+
deps.querier.update_wasm(move |wq| match wq {
197+
WasmQuery::Smart { contract_addr, .. }
198+
if contract_addr == AXELARNET_GATEWAY_ADDRESS =>
199+
{
200+
Ok(to_json_binary(&ChainName::try_from("axelar").unwrap()).into()).into()
201+
}
202+
_ => panic!("no mock for this query"),
203+
});
204+
205+
deps
206+
}
207+
}

contracts/interchain-token-service/src/contract/execute.rs

+126-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
use axelar_wasm_std::IntoContractError;
1+
use axelar_wasm_std::{nonempty, FnExt, IntoContractError};
22
use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage};
33
use error_stack::{bail, ensure, report, Result, ResultExt};
44
use router_api::{Address, ChainName, ChainNameRaw, CrossChainId};
5+
use sha3::{Digest, Keccak256};
56

67
use crate::events::Event;
78
use crate::primitives::HubMessage;
89
use crate::state::{self, load_config, load_its_contract};
10+
use crate::TokenId;
11+
12+
// this is just keccak256("its-interchain-token-id-gateway")
13+
const GATEWAY_TOKEN_PREFIX: [u8; 32] = [
14+
106, 80, 188, 250, 12, 170, 167, 223, 94, 185, 52, 185, 146, 147, 21, 23, 145, 36, 97, 146,
15+
215, 72, 32, 167, 6, 16, 83, 155, 176, 213, 112, 44,
16+
];
917

1018
#[derive(thiserror::Error, Debug, IntoContractError)]
1119
pub enum Error {
@@ -21,6 +29,10 @@ pub enum Error {
2129
FailedItsContractRegistration(ChainNameRaw),
2230
#[error("failed to deregister its contract for chain {0}")]
2331
FailedItsContractDeregistration(ChainNameRaw),
32+
#[error("failed to register gateway token")]
33+
FailedGatewayTokenRegistration,
34+
#[error("failed to generate token id")]
35+
FailedTokenIdGeneration,
2436
}
2537

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

127139
Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into()))
128140
}
141+
142+
pub fn register_gateway_token(
143+
deps: DepsMut,
144+
denom: nonempty::String,
145+
_chain: ChainNameRaw,
146+
) -> Result<Response, Error> {
147+
let token_id = gateway_token_id(&deps, &denom)?;
148+
state::save_gateway_token_denom(deps.storage, token_id, denom)
149+
.change_context(Error::FailedGatewayTokenRegistration)?;
150+
Ok(Response::new())
151+
}
152+
153+
pub fn gateway_token_id(deps: &DepsMut, denom: &str) -> Result<TokenId, Error> {
154+
let config = state::load_config(deps.storage);
155+
let gateway: axelarnet_gateway::Client =
156+
client::ContractClient::new(deps.querier, &config.axelarnet_gateway).into();
157+
let chain_name = gateway
158+
.chain_name()
159+
.change_context(Error::FailedTokenIdGeneration)?;
160+
let chain_name_hash: [u8; 32] = Keccak256::digest(chain_name.to_string().as_bytes()).into();
161+
162+
Keccak256::digest([&GATEWAY_TOKEN_PREFIX, &chain_name_hash, denom.as_bytes()].concat())
163+
.then(<[u8; 32]>::from)
164+
.then(TokenId::new)
165+
.then(Ok)
166+
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use assert_ok::assert_ok;
171+
use axelar_wasm_std::assert_err_contains;
172+
use axelarnet_gateway::msg::QueryMsg;
173+
use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier};
174+
use cosmwasm_std::{from_json, to_json_binary, Addr, MemoryStorage, OwnedDeps, WasmQuery};
175+
use router_api::{ChainName, ChainNameRaw};
176+
177+
use super::{gateway_token_id, register_gateway_token, Error};
178+
use crate::state::{self, Config};
179+
180+
#[test]
181+
fn gateway_token_id_should_be_idempotent() {
182+
let mut deps = init();
183+
let denom = "uaxl";
184+
let token_id = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
185+
let token_id_2 = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
186+
assert_eq!(token_id, token_id_2);
187+
}
188+
189+
#[test]
190+
fn gateway_token_id_should_differ_for_different_denoms() {
191+
let mut deps = init();
192+
let axl_denom = "uaxl";
193+
let eth_denom = "eth";
194+
let token_id_axl = assert_ok!(gateway_token_id(&deps.as_mut(), axl_denom));
195+
let token_id_eth = assert_ok!(gateway_token_id(&deps.as_mut(), eth_denom));
196+
assert_ne!(token_id_axl, token_id_eth);
197+
}
198+
199+
#[test]
200+
fn gateway_token_id_should_not_change() {
201+
let mut deps = init();
202+
let denom = "uaxl";
203+
let token_id = assert_ok!(gateway_token_id(&deps.as_mut(), denom));
204+
goldie::assert_json!(token_id);
205+
}
206+
207+
#[test]
208+
fn register_token_id_should_not_overwrite() {
209+
let mut deps = init();
210+
let denom = "uaxl";
211+
let chain = ChainNameRaw::try_from("ethereum").unwrap();
212+
assert_ok!(register_gateway_token(
213+
deps.as_mut(),
214+
denom.try_into().unwrap(),
215+
chain.clone()
216+
));
217+
// calling again should fail
218+
assert_err_contains!(
219+
register_gateway_token(deps.as_mut(), denom.try_into().unwrap(), chain),
220+
Error,
221+
Error::FailedGatewayTokenRegistration
222+
);
223+
}
224+
225+
fn init() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
226+
let addr = Addr::unchecked("axelar-gateway");
227+
let mut deps = mock_dependencies();
228+
state::save_config(
229+
deps.as_mut().storage,
230+
&Config {
231+
axelarnet_gateway: addr.clone(),
232+
},
233+
)
234+
.unwrap();
235+
236+
let mut querier = MockQuerier::default();
237+
querier.update_wasm(move |msg| match msg {
238+
WasmQuery::Smart { contract_addr, msg } if contract_addr == &addr.to_string() => {
239+
let msg = from_json::<QueryMsg>(msg).unwrap();
240+
match msg {
241+
QueryMsg::ChainName {} => {
242+
Ok(to_json_binary(&ChainName::try_from("axelar").unwrap()).into()).into()
243+
}
244+
_ => panic!("unsupported query"),
245+
}
246+
}
247+
_ => panic!("unexpected query: {:?}", msg),
248+
});
249+
250+
deps.querier = querier;
251+
deps
252+
}
253+
}

contracts/interchain-token-service/src/contract/query.rs

+5
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ pub fn all_its_contracts(deps: Deps) -> Result<Binary, state::Error> {
1212
let contract_addresses = state::load_all_its_contracts(deps.storage)?;
1313
Ok(to_json_binary(&contract_addresses)?)
1414
}
15+
16+
pub fn gateway_tokens(deps: Deps) -> Result<Binary, state::Error> {
17+
let tokens = state::load_all_gateway_tokens(deps.storage)?;
18+
Ok(to_json_binary(&tokens)?)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"12b0c9bff75ed8db58228ceff639071ca700a2b608f4f086f18f8208fbe9c3e0"

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

+12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use std::collections::HashMap;
22

3+
use axelar_wasm_std::nonempty;
34
use axelarnet_gateway::AxelarExecutableMsg;
45
use cosmwasm_schema::{cw_serde, QueryResponses};
56
use msgs_derive::EnsurePermissions;
67
use router_api::{Address, ChainNameRaw};
78

9+
use crate::TokenId;
10+
811
#[cw_serde]
912
pub struct InstantiateMsg {
1013
pub governance_address: String,
@@ -33,6 +36,13 @@ pub enum ExecuteMsg {
3336
/// The admin is allowed to remove the ITS address of a chain for emergencies.
3437
#[permission(Elevated)]
3538
DeregisterItsContract { chain: ChainNameRaw },
39+
40+
/// Register legacy gateway token with ITS
41+
#[permission(Governance)]
42+
RegisterGatewayToken {
43+
denom: nonempty::String,
44+
source_chain: ChainNameRaw,
45+
},
3646
}
3747

3848
#[cw_serde]
@@ -44,4 +54,6 @@ pub enum QueryMsg {
4454
/// Query all registererd ITS contract addresses
4555
#[returns(HashMap<ChainNameRaw, Address>)]
4656
AllItsContracts,
57+
#[returns(HashMap<nonempty::String, TokenId>)]
58+
GatewayTokens,
4759
}

contracts/interchain-token-service/src/primitives.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ use std::fmt::Display;
22

33
use cosmwasm_schema::cw_serde;
44
use cosmwasm_std::{HexBinary, Uint256};
5+
use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey};
56
use router_api::ChainNameRaw;
67
use strum::FromRepr;
78

89
/// A unique 32-byte identifier for linked cross-chain tokens across ITS contracts.
910
#[cw_serde]
10-
#[derive(Eq)]
11+
#[derive(Eq, Hash)]
1112
pub struct TokenId(
1213
#[serde(with = "axelar_wasm_std::hex")]
1314
#[schemars(with = "String")]
@@ -124,3 +125,22 @@ impl From<TokenId> for [u8; 32] {
124125
id.0
125126
}
126127
}
128+
129+
impl<'a> PrimaryKey<'a> for TokenId {
130+
type Prefix = ();
131+
type SubPrefix = ();
132+
type Suffix = Self;
133+
type SuperSuffix = Self;
134+
135+
fn key(&self) -> Vec<Key> {
136+
self.0.key()
137+
}
138+
}
139+
140+
impl KeyDeserialize for TokenId {
141+
type Output = TokenId;
142+
fn from_vec(value: Vec<u8>) -> cosmwasm_std::StdResult<Self::Output> {
143+
let inner = <[u8; 32]>::from_vec(value)?;
144+
Ok(TokenId(inner))
145+
}
146+
}

0 commit comments

Comments
 (0)