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