1use core::panic;
6use std::{collections::HashMap, str::from_utf8, time::Duration};
7
8use anyhow::anyhow;
9use async_trait::async_trait;
10use fastcrypto::traits::ToFromBytes;
11use iota_json_rpc_api::BridgeReadApiClient;
12use iota_json_rpc_types::{
13 DevInspectResults, EventFilter, EventPage, IotaEvent, IotaObjectDataOptions,
14 IotaTransactionBlockResponse, IotaTransactionBlockResponseOptions, Page,
15};
16use iota_sdk::{IotaClient as IotaSdkClient, IotaClientBuilder};
17use iota_types::{
18 BRIDGE_PACKAGE_ID, IOTA_BRIDGE_OBJECT_ID, Identifier, TypeTag,
19 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
20 bridge::{
21 BridgeSummary, BridgeTreasurySummary, MoveTypeCommitteeMember,
22 MoveTypeParsedTokenTransferMessage,
23 },
24 digests::TransactionDigest,
25 event::EventID,
26 gas_coin::GasCoin,
27 object::Owner,
28 parse_iota_type_tag,
29 transaction::{
30 Argument, CallArg, Command, ObjectArg, ProgrammableMoveCall, ProgrammableTransaction,
31 Transaction, TransactionKind,
32 },
33};
34use serde::de::DeserializeOwned;
35use tokio::sync::OnceCell;
36use tracing::{error, warn};
37
38use crate::{
39 crypto::BridgeAuthorityPublicKey,
40 error::{BridgeError, BridgeResult},
41 events::IotaBridgeEvent,
42 retry_with_max_elapsed_time,
43 types::{
44 BridgeAction, BridgeActionStatus, BridgeAuthority, BridgeCommittee,
45 ParsedTokenTransferMessage,
46 },
47};
48
49pub struct IotaClient<P> {
50 inner: P,
51}
52
53pub type IotaBridgeClient = IotaClient<IotaSdkClient>;
54
55impl IotaBridgeClient {
56 pub async fn new(rpc_url: &str) -> anyhow::Result<Self> {
57 let inner = IotaClientBuilder::default()
58 .build(rpc_url)
59 .await
60 .map_err(|e| {
61 anyhow!("Can't establish connection with IOTA Rpc {rpc_url}. Error: {e}")
62 })?;
63 let self_ = Self { inner };
64 self_.describe().await?;
65 Ok(self_)
66 }
67
68 pub fn iota_client(&self) -> &IotaSdkClient {
69 &self.inner
70 }
71}
72
73impl<P> IotaClient<P>
74where
75 P: IotaClientInner,
76{
77 pub fn new_for_testing(inner: P) -> Self {
78 Self { inner }
79 }
80
81 async fn describe(&self) -> anyhow::Result<()> {
83 let chain_id = self.inner.get_chain_identifier().await?;
84 let block_number = self.inner.get_latest_checkpoint_sequence_number().await?;
85 tracing::info!(
86 "IotaClient is connected to chain {chain_id}, current block number: {block_number}"
87 );
88 Ok(())
89 }
90
91 pub async fn get_mutable_bridge_object_arg_must_succeed(&self) -> ObjectArg {
97 static ARG: OnceCell<ObjectArg> = OnceCell::const_new();
98 *ARG.get_or_init(|| async move {
99 let Ok(Ok(bridge_object_arg)) = retry_with_max_elapsed_time!(
100 self.inner.get_mutable_bridge_object_arg(),
101 Duration::from_secs(30)
102 ) else {
103 panic!("Failed to get bridge object arg after retries");
104 };
105 bridge_object_arg
106 })
107 .await
108 }
109
110 pub async fn query_events_by_module(
112 &self,
113 package: ObjectID,
114 module: Identifier,
115 cursor: Option<EventID>,
117 ) -> BridgeResult<Page<IotaEvent, EventID>> {
118 let filter = EventFilter::MoveEventModule {
119 package,
120 module: module.clone(),
121 };
122 let events = self.inner.query_events(filter.clone(), cursor).await?;
123
124 assert!(
126 events
127 .data
128 .iter()
129 .all(|event| event.type_.address.as_ref() == package.as_ref()
130 && event.type_.module == module)
131 );
132 Ok(events)
133 }
134
135 pub async fn get_bridge_action_by_tx_digest_and_event_idx_maybe(
139 &self,
140 tx_digest: &TransactionDigest,
141 event_idx: u16,
142 ) -> BridgeResult<BridgeAction> {
143 let events = self.inner.get_events_by_tx_digest(*tx_digest).await?;
144 let event = events
145 .get(event_idx as usize)
146 .ok_or(BridgeError::NoBridgeEventsInTxPosition)?;
147 if event.type_.address.as_ref() != BRIDGE_PACKAGE_ID.as_ref() {
148 return Err(BridgeError::BridgeEventInUnrecognizedIotaPackage);
149 }
150 let bridge_event = IotaBridgeEvent::try_from_iota_event(event)?
151 .ok_or(BridgeError::NoBridgeEventsInTxPosition)?;
152
153 bridge_event
154 .try_into_bridge_action(*tx_digest, event_idx)
155 .ok_or(BridgeError::BridgeEventNotActionable)
156 }
157
158 pub async fn get_bridge_summary(&self) -> BridgeResult<BridgeSummary> {
159 self.inner
160 .get_bridge_summary()
161 .await
162 .map_err(|e| BridgeError::Internal(format!("Can't get bridge committee: {e}")))
163 }
164
165 pub async fn is_bridge_paused(&self) -> BridgeResult<bool> {
166 self.get_bridge_summary()
167 .await
168 .map(|summary| summary.is_frozen)
169 }
170
171 pub async fn get_treasury_summary(&self) -> BridgeResult<BridgeTreasurySummary> {
172 Ok(self.get_bridge_summary().await?.treasury)
173 }
174
175 pub async fn get_token_id_map(&self) -> BridgeResult<HashMap<u8, TypeTag>> {
176 self.get_bridge_summary()
177 .await?
178 .treasury
179 .id_token_type_map
180 .into_iter()
181 .map(|(id, name)| {
182 parse_iota_type_tag(&format!("0x{name}"))
183 .map(|name| (id, name))
184 .map_err(|e| {
185 BridgeError::Internal(format!(
186 "Failed to retrieve token id mapping: {e}, type name: {name}"
187 ))
188 })
189 })
190 .collect()
191 }
192
193 pub async fn get_notional_values(&self) -> BridgeResult<HashMap<u8, u64>> {
194 let bridge_summary = self.get_bridge_summary().await?;
195 bridge_summary
196 .treasury
197 .id_token_type_map
198 .iter()
199 .map(|(id, type_name)| {
200 bridge_summary
201 .treasury
202 .supported_tokens
203 .iter()
204 .find_map(|(tn, metadata)| {
205 if type_name == tn {
206 Some((*id, metadata.notional_value))
207 } else {
208 None
209 }
210 })
211 .ok_or(BridgeError::Internal(
212 "Error encountered when retrieving token notional values.".into(),
213 ))
214 })
215 .collect()
216 }
217
218 pub async fn get_bridge_committee(&self) -> BridgeResult<BridgeCommittee> {
219 let bridge_summary = self
220 .inner
221 .get_bridge_summary()
222 .await
223 .map_err(|e| BridgeError::Internal(format!("Can't get bridge committee: {e}")))?;
224 let move_type_bridge_committee = bridge_summary.committee;
225
226 let mut authorities = vec![];
227 for (_, member) in move_type_bridge_committee.members {
229 let MoveTypeCommitteeMember {
230 iota_address,
231 bridge_pubkey_bytes,
232 voting_power,
233 http_rest_url,
234 blocklisted,
235 } = member;
236 let pubkey = BridgeAuthorityPublicKey::from_bytes(&bridge_pubkey_bytes)?;
237 let base_url = from_utf8(&http_rest_url).unwrap_or_else(|_e| {
238 warn!(
239 "Bridge authority address: {}, pubkey: {:?} has invalid http url: {:?}",
240 iota_address, bridge_pubkey_bytes, http_rest_url
241 );
242 ""
243 });
244 authorities.push(BridgeAuthority {
245 pubkey,
246 voting_power,
247 base_url: base_url.into(),
248 is_blocklisted: blocklisted,
249 });
250 }
251 BridgeCommittee::new(authorities)
252 }
253
254 pub async fn get_chain_identifier(&self) -> BridgeResult<String> {
255 Ok(self.inner.get_chain_identifier().await?)
256 }
257
258 pub async fn get_reference_gas_price_until_success(&self) -> u64 {
259 loop {
260 let Ok(Ok(rgp)) = retry_with_max_elapsed_time!(
261 self.inner.get_reference_gas_price(),
262 Duration::from_secs(30)
263 ) else {
264 error!("Failed to get reference gas price");
266 continue;
267 };
268 return rgp;
269 }
270 }
271
272 pub async fn execute_transaction_block_with_effects(
273 &self,
274 tx: iota_types::transaction::Transaction,
275 ) -> BridgeResult<IotaTransactionBlockResponse> {
276 self.inner.execute_transaction_block_with_effects(tx).await
277 }
278
279 pub async fn get_token_transfer_action_onchain_status_until_success(
281 &self,
282 source_chain_id: u8,
283 seq_number: u64,
284 ) -> BridgeActionStatus {
285 loop {
286 let bridge_object_arg = self.get_mutable_bridge_object_arg_must_succeed().await;
287 let Ok(Ok(status)) = retry_with_max_elapsed_time!(
288 self.inner.get_token_transfer_action_onchain_status(
289 bridge_object_arg,
290 source_chain_id,
291 seq_number
292 ),
293 Duration::from_secs(30)
294 ) else {
295 error!(
297 source_chain_id,
298 seq_number, "Failed to get token transfer action onchain status"
299 );
300 continue;
301 };
302 return status;
303 }
304 }
305
306 pub async fn get_token_transfer_action_onchain_signatures_until_success(
307 &self,
308 source_chain_id: u8,
309 seq_number: u64,
310 ) -> Option<Vec<Vec<u8>>> {
311 loop {
312 let bridge_object_arg = self.get_mutable_bridge_object_arg_must_succeed().await;
313 let Ok(Ok(sigs)) = retry_with_max_elapsed_time!(
314 self.inner.get_token_transfer_action_onchain_signatures(
315 bridge_object_arg,
316 source_chain_id,
317 seq_number
318 ),
319 Duration::from_secs(30)
320 ) else {
321 error!(
323 source_chain_id,
324 seq_number, "Failed to get token transfer action onchain signatures"
325 );
326 continue;
327 };
328 return sigs;
329 }
330 }
331
332 pub async fn get_parsed_token_transfer_message(
333 &self,
334 source_chain_id: u8,
335 seq_number: u64,
336 ) -> BridgeResult<Option<ParsedTokenTransferMessage>> {
337 let bridge_object_arg = self.get_mutable_bridge_object_arg_must_succeed().await;
338 let message = self
339 .inner
340 .get_parsed_token_transfer_message(bridge_object_arg, source_chain_id, seq_number)
341 .await?;
342 Ok(match message {
343 Some(payload) => Some(ParsedTokenTransferMessage::try_from(payload)?),
344 None => None,
345 })
346 }
347
348 pub async fn get_gas_data_panic_if_not_gas(
349 &self,
350 gas_object_id: ObjectID,
351 ) -> (GasCoin, ObjectRef, Owner) {
352 self.inner
353 .get_gas_data_panic_if_not_gas(gas_object_id)
354 .await
355 }
356}
357
358#[async_trait]
361pub trait IotaClientInner: Send + Sync {
362 type Error: Into<anyhow::Error> + Send + Sync + std::error::Error + 'static;
363 async fn query_events(
364 &self,
365 query: EventFilter,
366 cursor: Option<EventID>,
367 ) -> Result<EventPage, Self::Error>;
368
369 async fn get_events_by_tx_digest(
370 &self,
371 tx_digest: TransactionDigest,
372 ) -> Result<Vec<IotaEvent>, Self::Error>;
373
374 async fn get_chain_identifier(&self) -> Result<String, Self::Error>;
375
376 async fn get_reference_gas_price(&self) -> Result<u64, Self::Error>;
377
378 async fn get_latest_checkpoint_sequence_number(&self) -> Result<u64, Self::Error>;
379
380 async fn get_mutable_bridge_object_arg(&self) -> Result<ObjectArg, Self::Error>;
381
382 async fn get_bridge_summary(&self) -> Result<BridgeSummary, Self::Error>;
383
384 async fn execute_transaction_block_with_effects(
385 &self,
386 tx: Transaction,
387 ) -> Result<IotaTransactionBlockResponse, BridgeError>;
388
389 async fn get_token_transfer_action_onchain_status(
390 &self,
391 bridge_object_arg: ObjectArg,
392 source_chain_id: u8,
393 seq_number: u64,
394 ) -> Result<BridgeActionStatus, BridgeError>;
395
396 async fn get_token_transfer_action_onchain_signatures(
397 &self,
398 bridge_object_arg: ObjectArg,
399 source_chain_id: u8,
400 seq_number: u64,
401 ) -> Result<Option<Vec<Vec<u8>>>, BridgeError>;
402
403 async fn get_parsed_token_transfer_message(
404 &self,
405 bridge_object_arg: ObjectArg,
406 source_chain_id: u8,
407 seq_number: u64,
408 ) -> Result<Option<MoveTypeParsedTokenTransferMessage>, BridgeError>;
409
410 async fn get_gas_data_panic_if_not_gas(
411 &self,
412 gas_object_id: ObjectID,
413 ) -> (GasCoin, ObjectRef, Owner);
414}
415
416#[async_trait]
417impl IotaClientInner for IotaSdkClient {
418 type Error = iota_sdk::error::Error;
419
420 async fn query_events(
421 &self,
422 query: EventFilter,
423 cursor: Option<EventID>,
424 ) -> Result<EventPage, Self::Error> {
425 self.event_api()
426 .query_events(query, cursor, None, false)
427 .await
428 }
429
430 async fn get_events_by_tx_digest(
431 &self,
432 tx_digest: TransactionDigest,
433 ) -> Result<Vec<IotaEvent>, Self::Error> {
434 self.event_api().get_events(tx_digest).await
435 }
436
437 async fn get_chain_identifier(&self) -> Result<String, Self::Error> {
438 self.read_api().get_chain_identifier().await
439 }
440
441 async fn get_reference_gas_price(&self) -> Result<u64, Self::Error> {
442 self.governance_api().get_reference_gas_price().await
443 }
444
445 async fn get_latest_checkpoint_sequence_number(&self) -> Result<u64, Self::Error> {
446 self.read_api()
447 .get_latest_checkpoint_sequence_number()
448 .await
449 }
450
451 async fn get_mutable_bridge_object_arg(&self) -> Result<ObjectArg, Self::Error> {
452 let initial_shared_version = self
453 .http()
454 .get_bridge_object_initial_shared_version()
455 .await?;
456 Ok(ObjectArg::SharedObject {
457 id: IOTA_BRIDGE_OBJECT_ID,
458 initial_shared_version: SequenceNumber::from_u64(initial_shared_version),
459 mutable: true,
460 })
461 }
462
463 async fn get_bridge_summary(&self) -> Result<BridgeSummary, Self::Error> {
464 self.http().get_latest_bridge().await.map_err(|e| e.into())
465 }
466
467 async fn get_token_transfer_action_onchain_status(
468 &self,
469 bridge_object_arg: ObjectArg,
470 source_chain_id: u8,
471 seq_number: u64,
472 ) -> Result<BridgeActionStatus, BridgeError> {
473 dev_inspect_bridge::<u8>(
474 self,
475 bridge_object_arg,
476 source_chain_id,
477 seq_number,
478 "get_token_transfer_action_status",
479 )
480 .await
481 .and_then(|status_byte| BridgeActionStatus::try_from(status_byte).map_err(Into::into))
482 }
483
484 async fn get_token_transfer_action_onchain_signatures(
485 &self,
486 bridge_object_arg: ObjectArg,
487 source_chain_id: u8,
488 seq_number: u64,
489 ) -> Result<Option<Vec<Vec<u8>>>, BridgeError> {
490 dev_inspect_bridge::<Option<Vec<Vec<u8>>>>(
491 self,
492 bridge_object_arg,
493 source_chain_id,
494 seq_number,
495 "get_token_transfer_action_signatures",
496 )
497 .await
498 }
499
500 async fn execute_transaction_block_with_effects(
501 &self,
502 tx: Transaction,
503 ) -> Result<IotaTransactionBlockResponse, BridgeError> {
504 match self.quorum_driver_api().execute_transaction_block(
505 tx,
506 IotaTransactionBlockResponseOptions::new().with_effects().with_events(),
507 Some(iota_types::quorum_driver_types::ExecuteTransactionRequestType::WaitForEffectsCert),
508 ).await {
509 Ok(response) => Ok(response),
510 Err(e) => return Err(BridgeError::IotaTxFailureGeneric(e.to_string())),
511 }
512 }
513
514 async fn get_parsed_token_transfer_message(
515 &self,
516 bridge_object_arg: ObjectArg,
517 source_chain_id: u8,
518 seq_number: u64,
519 ) -> Result<Option<MoveTypeParsedTokenTransferMessage>, BridgeError> {
520 dev_inspect_bridge::<Option<MoveTypeParsedTokenTransferMessage>>(
521 self,
522 bridge_object_arg,
523 source_chain_id,
524 seq_number,
525 "get_parsed_token_transfer_message",
526 )
527 .await
528 }
529
530 async fn get_gas_data_panic_if_not_gas(
531 &self,
532 gas_object_id: ObjectID,
533 ) -> (GasCoin, ObjectRef, Owner) {
534 loop {
535 match self
536 .read_api()
537 .get_object_with_options(
538 gas_object_id,
539 IotaObjectDataOptions::default().with_owner().with_content(),
540 )
541 .await
542 .map(|resp| resp.data)
543 {
544 Ok(Some(gas_obj)) => {
545 let owner = gas_obj.owner.expect("Owner is requested");
546 let gas_coin = GasCoin::try_from(&gas_obj)
547 .unwrap_or_else(|err| panic!("{} is not a gas coin: {err}", gas_object_id));
548 return (gas_coin, gas_obj.object_ref(), owner);
549 }
550 other => {
551 warn!("Can't get gas object: {:?}: {:?}", gas_object_id, other);
552 tokio::time::sleep(Duration::from_secs(5)).await;
553 }
554 }
555 }
556 }
557}
558
559async fn dev_inspect_bridge<T>(
563 iota_client: &IotaSdkClient,
564 bridge_object_arg: ObjectArg,
565 source_chain_id: u8,
566 seq_number: u64,
567 function_name: &str,
568) -> Result<T, BridgeError>
569where
570 T: DeserializeOwned,
571{
572 let pt = ProgrammableTransaction {
573 inputs: vec![
574 CallArg::Object(bridge_object_arg),
575 CallArg::Pure(bcs::to_bytes(&source_chain_id).unwrap()),
576 CallArg::Pure(bcs::to_bytes(&seq_number).unwrap()),
577 ],
578 commands: vec![Command::MoveCall(Box::new(ProgrammableMoveCall {
579 package: BRIDGE_PACKAGE_ID,
580 module: Identifier::new("bridge").unwrap(),
581 function: Identifier::new(function_name).unwrap(),
582 type_arguments: vec![],
583 arguments: vec![Argument::Input(0), Argument::Input(1), Argument::Input(2)],
584 }))],
585 };
586 let kind = TransactionKind::programmable(pt);
587 let resp = iota_client
588 .read_api()
589 .dev_inspect_transaction_block(IotaAddress::ZERO, kind, None, None, None)
590 .await?;
591 let DevInspectResults {
592 results, effects, ..
593 } = resp;
594 let Some(results) = results else {
595 return Err(BridgeError::Generic(format!(
596 "No results returned for '{}', effects: {:?}",
597 function_name, effects
598 )));
599 };
600 let return_values = &results
601 .first()
602 .ok_or(BridgeError::Generic(format!(
603 "No return values for '{}', results: {:?}",
604 function_name, results
605 )))?
606 .return_values;
607 let (value_bytes, _type_tag) = return_values.first().ok_or(BridgeError::Generic(format!(
608 "No first return value for '{}', results: {:?}",
609 function_name, results
610 )))?;
611 bcs::from_bytes::<T>(value_bytes).map_err(|e| {
612 BridgeError::Generic(format!(
613 "Failed to parse return value for '{}', error: {:?}, results: {:?}",
614 function_name, e, results
615 ))
616 })
617}
618
619#[cfg(test)]
620mod tests {
621 use std::str::FromStr;
622
623 use ethers::types::Address as EthAddress;
624 use iota_json_rpc_types::BcsEvent;
625 use iota_types::{
626 bridge::{BridgeChainId, TOKEN_ID_IOTA, TOKEN_ID_USDC},
627 crypto::get_key_pair,
628 };
629 use move_core_types::account_address::AccountAddress;
630 use serde::{Deserialize, Serialize};
631 use test_cluster::TestClusterBuilder;
632
633 use super::*;
634 use crate::{
635 BRIDGE_ENABLE_PROTOCOL_VERSION,
636 crypto::BridgeAuthorityKeyPair,
637 events::{
638 EmittedIotaToEthTokenBridgeV1, IotaToEthTokenBridgeV1, MoveTokenDepositedEvent,
639 init_all_struct_tags,
640 },
641 iota_mock_client::IotaMockClient,
642 test_utils::{
643 approve_action_with_validator_secrets, bridge_token,
644 get_test_eth_to_iota_bridge_action, get_test_iota_to_eth_bridge_action,
645 },
646 types::IotaToEthBridgeAction,
647 };
648
649 #[tokio::test]
650 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
651 async fn get_bridge_action_by_tx_digest_and_event_idx_maybe() {
652 telemetry_subscribers::init_for_testing();
656 let mock_client = IotaMockClient::default();
657 let iota_client = IotaClient::new_for_testing(mock_client.clone());
658 let tx_digest = TransactionDigest::random();
659
660 init_all_struct_tags();
662
663 let sanitized_event_1 = EmittedIotaToEthTokenBridgeV1 {
664 nonce: 1,
665 iota_chain_id: BridgeChainId::IotaTestnet,
666 iota_address: IotaAddress::random_for_testing_only(),
667 eth_chain_id: BridgeChainId::EthSepolia,
668 eth_address: EthAddress::random(),
669 token_id: TOKEN_ID_IOTA,
670 amount_iota_adjusted: 100,
671 };
672 let emitted_event_1 = MoveTokenDepositedEvent {
673 seq_num: sanitized_event_1.nonce,
674 source_chain: sanitized_event_1.iota_chain_id as u8,
675 sender_address: sanitized_event_1.iota_address.to_vec(),
676 target_chain: sanitized_event_1.eth_chain_id as u8,
677 target_address: sanitized_event_1.eth_address.as_bytes().to_vec(),
678 token_type: sanitized_event_1.token_id,
679 amount_iota_adjusted: sanitized_event_1.amount_iota_adjusted,
680 };
681
682 let mut iota_event_1 = IotaEvent::random_for_testing();
683 iota_event_1.type_ = IotaToEthTokenBridgeV1.get().unwrap().clone();
684 iota_event_1.bcs = BcsEvent::new(bcs::to_bytes(&emitted_event_1).unwrap());
685
686 #[derive(Serialize, Deserialize)]
687 struct RandomStruct {}
688
689 let event_2: RandomStruct = RandomStruct {};
690 let mut iota_event_2 = IotaEvent::random_for_testing();
692 iota_event_2.type_ = IotaToEthTokenBridgeV1.get().unwrap().clone();
693 iota_event_2.type_.module = Identifier::from_str("unrecognized_module").unwrap();
694 iota_event_2.bcs = BcsEvent::new(bcs::to_bytes(&event_2).unwrap());
695
696 let mut iota_event_3 = iota_event_1.clone();
698 iota_event_3.type_.address = AccountAddress::random();
699
700 mock_client.add_events_by_tx_digest(
701 tx_digest,
702 vec![
703 iota_event_1.clone(),
704 iota_event_2.clone(),
705 iota_event_1.clone(),
706 iota_event_3.clone(),
707 ],
708 );
709 let expected_action_1 = BridgeAction::IotaToEthBridgeAction(IotaToEthBridgeAction {
710 iota_tx_digest: tx_digest,
711 iota_tx_event_index: 0,
712 iota_bridge_event: sanitized_event_1.clone(),
713 });
714 assert_eq!(
715 iota_client
716 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 0)
717 .await
718 .unwrap(),
719 expected_action_1,
720 );
721 let expected_action_2 = BridgeAction::IotaToEthBridgeAction(IotaToEthBridgeAction {
722 iota_tx_digest: tx_digest,
723 iota_tx_event_index: 2,
724 iota_bridge_event: sanitized_event_1.clone(),
725 });
726 assert_eq!(
727 iota_client
728 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 2)
729 .await
730 .unwrap(),
731 expected_action_2,
732 );
733 assert!(matches!(
734 iota_client
735 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 1)
736 .await
737 .unwrap_err(),
738 BridgeError::NoBridgeEventsInTxPosition
739 ),);
740 assert!(matches!(
741 iota_client
742 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 3)
743 .await
744 .unwrap_err(),
745 BridgeError::BridgeEventInUnrecognizedIotaPackage
746 ),);
747 assert!(matches!(
748 iota_client
749 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 4)
750 .await
751 .unwrap_err(),
752 BridgeError::NoBridgeEventsInTxPosition
753 ),);
754
755 iota_event_2.type_ = IotaToEthTokenBridgeV1.get().unwrap().clone();
757 mock_client.add_events_by_tx_digest(tx_digest, vec![iota_event_2]);
758 iota_client
759 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, 2)
760 .await
761 .unwrap_err();
762 }
763
764 #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
769 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
770 async fn test_get_action_onchain_status_for_iota_to_eth_transfer() {
771 telemetry_subscribers::init_for_testing();
772 let mut bridge_keys = vec![];
773 for _ in 0..=3 {
774 let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
775 bridge_keys.push(kp);
776 }
777 let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
778 .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
779 .build_with_bridge(bridge_keys, true)
780 .await;
781
782 let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
783 .await
784 .unwrap();
785 let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
786
787 test_cluster
789 .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
790 .await;
791 let context = &mut test_cluster.wallet;
792 let sender = context.active_address().unwrap();
793 let usdc_amount = 5000000;
794 let bridge_object_arg = iota_client
795 .get_mutable_bridge_object_arg_must_succeed()
796 .await;
797 let id_token_map = iota_client.get_token_id_map().await.unwrap();
798
799 let action =
802 get_test_eth_to_iota_bridge_action(None, Some(usdc_amount), Some(sender), None);
803 let usdc_object_ref = approve_action_with_validator_secrets(
804 context,
805 bridge_object_arg,
806 action.clone(),
807 &bridge_authority_keys,
808 Some(sender),
809 &id_token_map,
810 )
811 .await
812 .unwrap();
813
814 let status = iota_client
815 .inner
816 .get_token_transfer_action_onchain_status(
817 bridge_object_arg,
818 action.chain_id() as u8,
819 action.seq_number(),
820 )
821 .await
822 .unwrap();
823 assert_eq!(status, BridgeActionStatus::Claimed);
824
825 let eth_recv_address = EthAddress::random();
829 let bridge_event = bridge_token(
830 context,
831 eth_recv_address,
832 usdc_object_ref,
833 id_token_map.get(&TOKEN_ID_USDC).unwrap().clone(),
834 bridge_object_arg,
835 )
836 .await;
837 assert_eq!(bridge_event.nonce, 0);
838 assert_eq!(bridge_event.iota_chain_id, BridgeChainId::IotaCustom);
839 assert_eq!(bridge_event.eth_chain_id, BridgeChainId::EthCustom);
840 assert_eq!(bridge_event.eth_address, eth_recv_address);
841 assert_eq!(bridge_event.iota_address, sender);
842 assert_eq!(bridge_event.token_id, TOKEN_ID_USDC);
843 assert_eq!(bridge_event.amount_iota_adjusted, usdc_amount);
844
845 let action = get_test_iota_to_eth_bridge_action(
846 None,
847 None,
848 Some(bridge_event.nonce),
849 Some(bridge_event.amount_iota_adjusted),
850 Some(bridge_event.iota_address),
851 Some(bridge_event.eth_address),
852 Some(TOKEN_ID_USDC),
853 );
854 let status = iota_client
855 .inner
856 .get_token_transfer_action_onchain_status(
857 bridge_object_arg,
858 action.chain_id() as u8,
859 action.seq_number(),
860 )
861 .await
862 .unwrap();
863 assert_eq!(status, BridgeActionStatus::Pending);
865
866 approve_action_with_validator_secrets(
868 context,
869 bridge_object_arg,
870 action.clone(),
871 &bridge_authority_keys,
872 None,
873 &id_token_map,
874 )
875 .await;
876
877 let status = iota_client
878 .inner
879 .get_token_transfer_action_onchain_status(
880 bridge_object_arg,
881 action.chain_id() as u8,
882 action.seq_number(),
883 )
884 .await
885 .unwrap();
886 assert_eq!(status, BridgeActionStatus::Approved);
887
888 let action =
890 get_test_iota_to_eth_bridge_action(None, None, Some(100), None, None, None, None);
891 let status = iota_client
892 .inner
893 .get_token_transfer_action_onchain_status(
894 bridge_object_arg,
895 action.chain_id() as u8,
896 action.seq_number(),
897 )
898 .await
899 .unwrap();
900 assert_eq!(status, BridgeActionStatus::NotFound);
901 }
902}