1use soroban_token_sdk::metadata::TokenMetadata;
2use stellar_axelar_gas_service::AxelarGasServiceClient;
3use stellar_axelar_gateway::executable::{AxelarExecutableInterface, CustomAxelarExecutable};
4use stellar_axelar_gateway::AxelarGatewayMessagingClient;
5use stellar_axelar_std::address::AddressExt;
6use stellar_axelar_std::events::Event;
7use stellar_axelar_std::token::StellarAssetClient;
8use stellar_axelar_std::types::Token;
9use stellar_axelar_std::xdr::ToXdr;
10use stellar_axelar_std::{
11 contract, contractimpl, ensure, interfaces, only_operator, only_owner, soroban_sdk, vec,
12 when_not_paused, Address, AxelarExecutable, Bytes, BytesN, Env, IntoVal, Operatable, Ownable,
13 Pausable, String, Symbol, Upgradable, Val,
14};
15use stellar_interchain_token::InterchainTokenClient;
16
17use crate::error::ContractError;
18use crate::event::{
19 InterchainTokenDeploymentStartedEvent, InterchainTransferReceivedEvent,
20 InterchainTransferSentEvent, TrustedChainRemovedEvent, TrustedChainSetEvent,
21};
22use crate::flow_limit::FlowDirection;
23use crate::interface::InterchainTokenServiceInterface;
24use crate::storage::{self, TokenIdConfigValue};
25use crate::token_metadata::TokenMetadataExt;
26use crate::types::{
27 DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType,
28};
29use crate::{deployer, flow_limit, migrate, token_handler, token_id, token_metadata};
30
31const ITS_HUB_CHAIN_NAME: &str = "axelar";
32const EXECUTE_WITH_INTERCHAIN_TOKEN: &str = "execute_with_interchain_token";
33
34#[contract]
35#[derive(Operatable, Ownable, Pausable, Upgradable, AxelarExecutable)]
36#[migratable]
37pub struct InterchainTokenService;
38
39#[contractimpl]
40impl InterchainTokenService {
41 pub fn __constructor(
42 env: Env,
43 owner: Address,
44 operator: Address,
45 gateway: Address,
46 gas_service: Address,
47 its_hub_address: String,
48 chain_name: String,
49 native_token_address: Address,
50 interchain_token_wasm_hash: BytesN<32>,
51 token_manager_wasm_hash: BytesN<32>,
52 ) {
53 interfaces::set_owner(&env, &owner);
54 interfaces::set_operator(&env, &operator);
55 storage::set_gateway(&env, &gateway);
56 storage::set_gas_service(&env, &gas_service);
57 storage::set_its_hub_address(&env, &its_hub_address);
58 storage::set_chain_name(&env, &chain_name);
59 storage::set_native_token_address(&env, &native_token_address);
60 storage::set_interchain_token_wasm_hash(&env, &interchain_token_wasm_hash);
61 storage::set_token_manager_wasm_hash(&env, &token_manager_wasm_hash);
62 }
63}
64
65#[contractimpl]
66impl InterchainTokenServiceInterface for InterchainTokenService {
67 fn gas_service(env: &Env) -> Address {
68 storage::gas_service(env)
69 }
70
71 fn chain_name(env: &Env) -> String {
72 storage::chain_name(env)
73 }
74
75 fn its_hub_chain_name(env: &Env) -> String {
76 String::from_str(env, ITS_HUB_CHAIN_NAME)
77 }
78
79 fn its_hub_address(env: &Env) -> String {
80 storage::its_hub_address(env)
81 }
82
83 fn native_token_address(env: &Env) -> Address {
84 storage::native_token_address(env)
85 }
86
87 fn interchain_token_wasm_hash(env: &Env) -> BytesN<32> {
88 storage::interchain_token_wasm_hash(env)
89 }
90
91 fn token_manager_wasm_hash(env: &Env) -> BytesN<32> {
92 storage::token_manager_wasm_hash(env)
93 }
94
95 fn is_trusted_chain(env: &Env, chain: String) -> bool {
96 storage::is_trusted_chain(env, chain)
97 }
98
99 #[only_operator]
100 fn set_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError> {
101 ensure!(
102 !storage::is_trusted_chain(env, chain.clone()),
103 ContractError::TrustedChainAlreadySet
104 );
105
106 storage::set_trusted_chain_status(env, chain.clone());
107
108 TrustedChainSetEvent { chain }.emit(env);
109
110 Ok(())
111 }
112
113 #[only_operator]
114 fn remove_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError> {
115 ensure!(
116 storage::is_trusted_chain(env, chain.clone()),
117 ContractError::TrustedChainNotSet
118 );
119
120 storage::remove_trusted_chain_status(env, chain.clone());
121
122 TrustedChainRemovedEvent { chain }.emit(env);
123
124 Ok(())
125 }
126
127 fn interchain_token_id(env: &Env, deployer: Address, salt: BytesN<32>) -> BytesN<32> {
128 token_id::interchain_token_id(env, Self::chain_name_hash(env), deployer, salt)
129 }
130
131 fn canonical_interchain_token_id(env: &Env, token_address: Address) -> BytesN<32> {
132 token_id::canonical_interchain_token_id(env, Self::chain_name_hash(env), token_address)
133 }
134
135 fn interchain_token_address(env: &Env, token_id: BytesN<32>) -> Address {
136 deployer::interchain_token_address(env, token_id)
137 }
138
139 fn token_manager_address(env: &Env, token_id: BytesN<32>) -> Address {
140 deployer::token_manager_address(env, token_id)
141 }
142
143 fn registered_token_address(env: &Env, token_id: BytesN<32>) -> Address {
144 storage::token_id_config(env, token_id).token_address
145 }
146
147 fn deployed_token_manager(env: &Env, token_id: BytesN<32>) -> Address {
148 storage::token_id_config(env, token_id).token_manager
149 }
150
151 fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType {
152 storage::token_id_config(env, token_id).token_manager_type
153 }
154
155 fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option<i128> {
156 flow_limit::flow_limit(env, token_id)
157 }
158
159 fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 {
160 flow_limit::flow_out_amount(env, token_id)
161 }
162
163 fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 {
164 flow_limit::flow_in_amount(env, token_id)
165 }
166
167 #[only_operator]
168 fn set_flow_limit(
169 env: &Env,
170 token_id: BytesN<32>,
171 flow_limit: Option<i128>,
172 ) -> Result<(), ContractError> {
173 flow_limit::set_flow_limit(env, token_id, flow_limit)
174 }
175
176 #[when_not_paused]
177 fn deploy_interchain_token(
178 env: &Env,
179 caller: Address,
180 salt: BytesN<32>,
181 token_metadata: TokenMetadata,
182 initial_supply: i128,
183 minter: Option<Address>,
184 ) -> Result<BytesN<32>, ContractError> {
185 caller.require_auth();
186
187 ensure!(initial_supply >= 0, ContractError::InvalidInitialSupply);
188 ensure!(
189 initial_supply > 0 || minter.is_some(),
190 ContractError::InvalidTokenConfig
191 );
192
193 let token_id = Self::interchain_token_id(env, caller.clone(), salt);
194
195 token_metadata.validate()?;
196
197 let token_address = Self::deploy_token(env, token_id.clone(), token_metadata, minter)?;
198
199 if initial_supply > 0 {
200 StellarAssetClient::new(env, &token_address).mint(&caller, &initial_supply);
201 }
202
203 Ok(token_id)
204 }
205
206 #[when_not_paused]
207 fn deploy_remote_interchain_token(
208 env: &Env,
209 caller: Address,
210 salt: BytesN<32>,
211 destination_chain: String,
212 gas_token: Option<Token>,
213 ) -> Result<BytesN<32>, ContractError> {
214 caller.require_auth();
215
216 let token_id = Self::interchain_token_id(env, caller.clone(), salt);
217
218 Self::deploy_remote_token(env, caller, token_id.clone(), destination_chain, gas_token)?;
219
220 Ok(token_id)
221 }
222
223 #[when_not_paused]
224 fn register_canonical_token(
225 env: &Env,
226 token_address: Address,
227 ) -> Result<BytesN<32>, ContractError> {
228 let _ =
230 token_metadata::token_metadata(env, &token_address, &Self::native_token_address(env))?;
231
232 let token_id = Self::canonical_interchain_token_id(env, token_address.clone());
233
234 Self::ensure_token_not_registered(env, token_id.clone())?;
235
236 let _: Address = Self::deploy_token_manager(
237 env,
238 token_id.clone(),
239 token_address,
240 TokenManagerType::LockUnlock,
241 );
242
243 Ok(token_id)
244 }
245
246 #[when_not_paused]
247 fn deploy_remote_canonical_token(
248 env: &Env,
249 token_address: Address,
250 destination_chain: String,
251 spender: Address,
252 gas_token: Option<Token>,
253 ) -> Result<BytesN<32>, ContractError> {
254 spender.require_auth();
255
256 let token_id = Self::canonical_interchain_token_id(env, token_address);
257
258 Self::deploy_remote_token(env, spender, token_id.clone(), destination_chain, gas_token)?;
259
260 Ok(token_id)
261 }
262
263 #[when_not_paused]
264 fn interchain_transfer(
265 env: &Env,
266 caller: Address,
267 token_id: BytesN<32>,
268 destination_chain: String,
269 destination_address: Bytes,
270 amount: i128,
271 data: Option<Bytes>,
272 gas_token: Option<Token>,
273 ) -> Result<(), ContractError> {
274 ensure!(amount > 0, ContractError::InvalidAmount);
275
276 ensure!(
277 !destination_address.is_empty(),
278 ContractError::InvalidDestinationAddress
279 );
280
281 if let Some(ref data) = data {
282 ensure!(!data.is_empty(), ContractError::InvalidData);
283 }
284
285 caller.require_auth();
286
287 token_handler::take_token(
288 env,
289 &caller,
290 Self::token_id_config(env, token_id.clone())?,
291 amount,
292 )?;
293
294 FlowDirection::Out.add_flow(env, token_id.clone(), amount)?;
295
296 InterchainTransferSentEvent {
297 token_id: token_id.clone(),
298 source_address: caller.clone(),
299 destination_chain: destination_chain.clone(),
300 destination_address: destination_address.clone(),
301 amount,
302 data: data.clone(),
303 }
304 .emit(env);
305
306 let message = Message::InterchainTransfer(InterchainTransfer {
307 token_id,
308 source_address: caller.to_string_bytes(),
309 destination_address,
310 amount,
311 data,
312 });
313
314 Self::pay_gas_and_call_contract(env, caller, destination_chain, message, gas_token)?;
315
316 Ok(())
317 }
318
319 #[only_owner]
320 fn migrate_token(
321 env: &Env,
322 token_id: BytesN<32>,
323 upgrader: Address,
324 new_version: String,
325 ) -> Result<(), ContractError> {
326 migrate::migrate_token(env, token_id, upgrader, new_version)
327 }
328}
329
330impl InterchainTokenService {
331 fn pay_gas_and_call_contract(
332 env: &Env,
333 caller: Address,
334 destination_chain: String,
335 message: Message,
336 gas_token: Option<Token>,
337 ) -> Result<(), ContractError> {
338 ensure!(
339 Self::is_trusted_chain(env, destination_chain.clone()),
340 ContractError::UntrustedChain
341 );
342
343 let gateway = AxelarGatewayMessagingClient::new(env, &Self::gateway(env));
344 let gas_service = AxelarGasServiceClient::new(env, &Self::gas_service(env));
345
346 let payload = HubMessage::SendToHub {
347 destination_chain,
348 message,
349 }
350 .abi_encode(env)?;
351
352 let hub_chain = Self::its_hub_chain_name(env);
353 let hub_address = Self::its_hub_address(env);
354
355 if let Some(gas_token) = gas_token {
356 gas_service.pay_gas(
357 &env.current_contract_address(),
358 &hub_chain,
359 &hub_address,
360 &payload,
361 &caller,
362 &gas_token,
363 &Bytes::new(env),
364 );
365 }
366
367 gateway.call_contract(
368 &env.current_contract_address(),
369 &hub_chain,
370 &hub_address,
371 &payload,
372 );
373
374 Ok(())
375 }
376
377 fn get_execute_params(
379 env: &Env,
380 source_chain: String,
381 source_address: String,
382 payload: Bytes,
383 ) -> Result<(String, Message), ContractError> {
384 ensure!(
385 source_chain == Self::its_hub_chain_name(env),
386 ContractError::NotHubChain
387 );
388 ensure!(
389 source_address == Self::its_hub_address(env),
390 ContractError::NotHubAddress
391 );
392
393 let HubMessage::ReceiveFromHub {
394 source_chain: original_source_chain,
395 message,
396 } = HubMessage::abi_decode(env, &payload)?
397 else {
398 return Err(ContractError::InvalidMessageType);
399 };
400 ensure!(
401 storage::is_trusted_chain(env, original_source_chain.clone()),
402 ContractError::UntrustedChain
403 );
404
405 Ok((original_source_chain, message))
406 }
407
408 fn set_token_id_config(env: &Env, token_id: BytesN<32>, token_data: TokenIdConfigValue) {
409 storage::set_token_id_config(env, token_id, &token_data);
410 }
411
412 fn token_id_config(
423 env: &Env,
424 token_id: BytesN<32>,
425 ) -> Result<TokenIdConfigValue, ContractError> {
426 storage::try_token_id_config(env, token_id).ok_or(ContractError::InvalidTokenId)
427 }
428
429 fn chain_name_hash(env: &Env) -> BytesN<32> {
430 let chain_name = Self::chain_name(env);
431 env.crypto().keccak256(&chain_name.to_xdr(env)).into()
432 }
433
434 fn deploy_remote_token(
455 env: &Env,
456 caller: Address,
457 token_id: BytesN<32>,
458 destination_chain: String,
459 gas_token: Option<Token>,
460 ) -> Result<(), ContractError> {
461 ensure!(
462 destination_chain != Self::chain_name(env),
463 ContractError::InvalidDestinationChain
464 );
465
466 let token_address = Self::token_id_config(env, token_id.clone())?.token_address;
467 let TokenMetadata {
468 name,
469 symbol,
470 decimal,
471 } = token_metadata::token_metadata(env, &token_address, &Self::native_token_address(env))?;
472
473 let message = Message::DeployInterchainToken(DeployInterchainToken {
474 token_id: token_id.clone(),
475 name: name.clone(),
476 symbol: symbol.clone(),
477 decimals: decimal as u8,
478 minter: None,
479 });
480
481 InterchainTokenDeploymentStartedEvent {
482 token_id,
483 token_address,
484 destination_chain: destination_chain.clone(),
485 name,
486 symbol,
487 decimals: decimal,
488 minter: None,
489 }
490 .emit(env);
491
492 Self::pay_gas_and_call_contract(env, caller, destination_chain, message, gas_token)?;
493
494 Ok(())
495 }
496
497 fn execute_transfer_message(
498 env: &Env,
499 source_chain: &String,
500 message_id: String,
501 InterchainTransfer {
502 token_id,
503 source_address,
504 destination_address,
505 amount,
506 data,
507 }: InterchainTransfer,
508 ) -> Result<(), ContractError> {
509 ensure!(amount > 0, ContractError::InvalidAmount);
510
511 let destination_address = Address::from_string_bytes(&destination_address);
512
513 let token_config_value = Self::token_id_config(env, token_id.clone())?;
514 let token_address = token_config_value.token_address.clone();
515
516 FlowDirection::In.add_flow(env, token_id.clone(), amount)?;
517
518 token_handler::give_token(env, &destination_address, token_config_value, amount)?;
519
520 InterchainTransferReceivedEvent {
521 source_chain: source_chain.clone(),
522 token_id: token_id.clone(),
523 source_address: source_address.clone(),
524 destination_address: destination_address.clone(),
525 amount,
526 data_hash: data.as_ref().map(|d| env.crypto().keccak256(d).into()),
527 }
528 .emit(env);
529
530 if let Some(payload) = data {
531 Self::execute_contract_with_token(
532 env,
533 destination_address,
534 source_chain,
535 message_id,
536 source_address,
537 payload,
538 token_id,
539 token_address,
540 amount,
541 );
542 }
543
544 Ok(())
545 }
546
547 fn execute_contract_with_token(
548 env: &Env,
549 destination_address: Address,
550 source_chain: &String,
551 message_id: String,
552 source_address: Bytes,
553 payload: Bytes,
554 token_id: BytesN<32>,
555 token_address: Address,
556 amount: i128,
557 ) {
558 env.invoke_contract::<Val>(
561 &destination_address,
562 &Symbol::new(env, EXECUTE_WITH_INTERCHAIN_TOKEN),
563 vec![
564 env,
565 source_chain.to_val(),
566 message_id.to_val(),
567 source_address.to_val(),
568 payload.to_val(),
569 token_id.to_val(),
570 token_address.to_val(),
571 amount.into_val(env),
572 ],
573 );
574 }
575
576 fn execute_deploy_message(
577 env: &Env,
578 DeployInterchainToken {
579 token_id,
580 name,
581 symbol,
582 decimals,
583 minter,
584 }: DeployInterchainToken,
585 ) -> Result<(), ContractError> {
586 let token_metadata = TokenMetadata::new(name, symbol, decimals as u32)?;
587
588 let minter = minter.map(|m| Address::from_string_bytes(&m));
590
591 let _: Address = Self::deploy_token(env, token_id, token_metadata, minter)?;
592
593 Ok(())
594 }
595
596 fn deploy_token_manager(
597 env: &Env,
598 token_id: BytesN<32>,
599 token_address: Address,
600 token_manager_type: TokenManagerType,
601 ) -> Address {
602 let token_manager = deployer::deploy_token_manager(
603 env,
604 Self::token_manager_wasm_hash(env),
605 token_id.clone(),
606 token_address.clone(),
607 token_manager_type,
608 );
609
610 Self::set_token_id_config(
611 env,
612 token_id,
613 TokenIdConfigValue {
614 token_address,
615 token_manager: token_manager.clone(),
616 token_manager_type,
617 },
618 );
619
620 token_manager
621 }
622
623 fn deploy_token(
630 env: &Env,
631 token_id: BytesN<32>,
632 token_metadata: TokenMetadata,
633 minter: Option<Address>,
634 ) -> Result<Address, ContractError> {
635 Self::ensure_token_not_registered(env, token_id.clone())?;
636
637 let token_address = deployer::deploy_interchain_token(
638 env,
639 Self::interchain_token_wasm_hash(env),
640 minter,
641 token_id.clone(),
642 token_metadata,
643 );
644 let interchain_token_client = InterchainTokenClient::new(env, &token_address);
645
646 let token_manager = Self::deploy_token_manager(
647 env,
648 token_id,
649 token_address.clone(),
650 TokenManagerType::NativeInterchainToken,
651 );
652
653 if !interchain_token_client.is_minter(&token_manager) {
656 interchain_token_client.add_minter(&token_manager);
657 }
658
659 Ok(token_address)
660 }
661
662 fn ensure_token_not_registered(env: &Env, token_id: BytesN<32>) -> Result<(), ContractError> {
663 ensure!(
664 storage::try_token_id_config(env, token_id).is_none(),
665 ContractError::TokenAlreadyRegistered
666 );
667
668 Ok(())
669 }
670}
671
672impl CustomAxelarExecutable for InterchainTokenService {
673 type Error = ContractError;
674
675 fn __gateway(env: &Env) -> Address {
676 storage::gateway(env)
677 }
678
679 #[when_not_paused]
680 fn __execute(
681 env: &Env,
682 source_chain: String,
683 message_id: String,
684 source_address: String,
685 payload: Bytes,
686 ) -> Result<(), Self::Error> {
687 let (source_chain, message) =
688 Self::get_execute_params(env, source_chain, source_address, payload)?;
689
690 match message {
691 Message::InterchainTransfer(message) => {
692 Self::execute_transfer_message(env, &source_chain, message_id, message)
693 }
694 Message::DeployInterchainToken(message) => Self::execute_deploy_message(env, message),
695 }?;
696
697 Ok(())
698 }
699}