iota_bridge/
iota_transaction_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::HashMap, str::FromStr};
6
7use fastcrypto::traits::ToFromBytes;
8use iota_types::{
9    BRIDGE_PACKAGE_ID, Identifier, TypeTag,
10    base_types::{IotaAddress, ObjectRef},
11    bridge::{
12        BRIDGE_CREATE_ADD_TOKEN_ON_IOTA_MESSAGE_FUNCTION_NAME,
13        BRIDGE_EXECUTE_SYSTEM_MESSAGE_FUNCTION_NAME, BRIDGE_MESSAGE_MODULE_NAME,
14        BRIDGE_MODULE_NAME,
15    },
16    programmable_transaction_builder::ProgrammableTransactionBuilder,
17    transaction::{CallArg, ObjectArg, TransactionData},
18};
19use move_core_types::ident_str;
20
21use crate::{
22    error::{BridgeError, BridgeResult},
23    types::{BridgeAction, VerifiedCertifiedBridgeAction},
24};
25
26pub fn build_iota_transaction(
27    client_address: IotaAddress,
28    gas_object_ref: &ObjectRef,
29    action: VerifiedCertifiedBridgeAction,
30    bridge_object_arg: ObjectArg,
31    iota_token_type_tags: &HashMap<u8, TypeTag>,
32    rgp: u64,
33) -> BridgeResult<TransactionData> {
34    // TODO: Check chain id?
35    match action.data() {
36        BridgeAction::EthToIotaBridgeAction(_) => build_token_bridge_approve_transaction(
37            client_address,
38            gas_object_ref,
39            action,
40            true,
41            bridge_object_arg,
42            iota_token_type_tags,
43            rgp,
44        ),
45        BridgeAction::IotaToEthBridgeAction(_) => build_token_bridge_approve_transaction(
46            client_address,
47            gas_object_ref,
48            action,
49            false,
50            bridge_object_arg,
51            iota_token_type_tags,
52            rgp,
53        ),
54        BridgeAction::BlocklistCommitteeAction(_) => build_committee_blocklist_approve_transaction(
55            client_address,
56            gas_object_ref,
57            action,
58            bridge_object_arg,
59            rgp,
60        ),
61        BridgeAction::EmergencyAction(_) => build_emergency_op_approve_transaction(
62            client_address,
63            gas_object_ref,
64            action,
65            bridge_object_arg,
66            rgp,
67        ),
68        BridgeAction::LimitUpdateAction(_) => build_limit_update_approve_transaction(
69            client_address,
70            gas_object_ref,
71            action,
72            bridge_object_arg,
73            rgp,
74        ),
75        BridgeAction::AssetPriceUpdateAction(_) => build_asset_price_update_approve_transaction(
76            client_address,
77            gas_object_ref,
78            action,
79            bridge_object_arg,
80            rgp,
81        ),
82        BridgeAction::EvmContractUpgradeAction(_) => {
83            // It does not need an IOTA transaction to execute EVM contract upgrade
84            unreachable!()
85        }
86        BridgeAction::AddTokensOnIotaAction(_) => build_add_tokens_on_iota_transaction(
87            client_address,
88            gas_object_ref,
89            action,
90            bridge_object_arg,
91            rgp,
92        ),
93        BridgeAction::AddTokensOnEvmAction(_) => {
94            // It does not need an IOTA transaction to add tokens on EVM
95            unreachable!()
96        }
97    }
98}
99
100fn build_token_bridge_approve_transaction(
101    client_address: IotaAddress,
102    gas_object_ref: &ObjectRef,
103    action: VerifiedCertifiedBridgeAction,
104    claim: bool,
105    bridge_object_arg: ObjectArg,
106    iota_token_type_tags: &HashMap<u8, TypeTag>,
107    rgp: u64,
108) -> BridgeResult<TransactionData> {
109    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
110    let mut builder = ProgrammableTransactionBuilder::new();
111
112    let (source_chain, seq_num, sender, target_chain, target, token_type, amount) =
113        match bridge_action {
114            BridgeAction::IotaToEthBridgeAction(a) => {
115                let bridge_event = a.iota_bridge_event;
116                (
117                    bridge_event.iota_chain_id,
118                    bridge_event.nonce,
119                    bridge_event.iota_address.to_vec(),
120                    bridge_event.eth_chain_id,
121                    bridge_event.eth_address.to_fixed_bytes().to_vec(),
122                    bridge_event.token_id,
123                    bridge_event.amount_iota_adjusted,
124                )
125            }
126            BridgeAction::EthToIotaBridgeAction(a) => {
127                let bridge_event = a.eth_bridge_event;
128                (
129                    bridge_event.eth_chain_id,
130                    bridge_event.nonce,
131                    bridge_event.eth_address.to_fixed_bytes().to_vec(),
132                    bridge_event.iota_chain_id,
133                    bridge_event.iota_address.to_vec(),
134                    bridge_event.token_id,
135                    bridge_event.iota_adjusted_amount,
136                )
137            }
138            _ => unreachable!(),
139        };
140
141    let source_chain = builder.pure(source_chain as u8).unwrap();
142    let seq_num = builder.pure(seq_num).unwrap();
143    let sender = builder.pure(sender.clone()).map_err(|e| {
144        BridgeError::BridgeSerialization(format!(
145            "Failed to serialize sender: {:?}. Err: {:?}",
146            sender, e
147        ))
148    })?;
149    let target_chain = builder.pure(target_chain as u8).unwrap();
150    let target = builder.pure(target.clone()).map_err(|e| {
151        BridgeError::BridgeSerialization(format!(
152            "Failed to serialize target: {:?}. Err: {:?}",
153            target, e
154        ))
155    })?;
156    let arg_token_type = builder.pure(token_type).unwrap();
157    let amount = builder.pure(amount).unwrap();
158
159    let arg_msg = builder.programmable_move_call(
160        BRIDGE_PACKAGE_ID,
161        ident_str!("message").to_owned(),
162        ident_str!("create_token_bridge_message").to_owned(),
163        vec![],
164        vec![
165            source_chain,
166            seq_num,
167            sender,
168            target_chain,
169            target,
170            arg_token_type,
171            amount,
172        ],
173    );
174
175    // Unwrap: these should not fail
176    let arg_bridge = builder.obj(bridge_object_arg).unwrap();
177    let arg_clock = builder.input(CallArg::CLOCK_IMM).unwrap();
178
179    let mut sig_bytes = vec![];
180    for (_, sig) in sigs.signatures {
181        sig_bytes.push(sig.as_bytes().to_vec());
182    }
183    let arg_signatures = builder.pure(sig_bytes.clone()).map_err(|e| {
184        BridgeError::BridgeSerialization(format!(
185            "Failed to serialize signatures: {:?}. Err: {:?}",
186            sig_bytes, e
187        ))
188    })?;
189
190    builder.programmable_move_call(
191        BRIDGE_PACKAGE_ID,
192        iota_types::bridge::BRIDGE_MODULE_NAME.to_owned(),
193        ident_str!("approve_token_transfer").to_owned(),
194        vec![],
195        vec![arg_bridge, arg_msg, arg_signatures],
196    );
197
198    if claim {
199        builder.programmable_move_call(
200            BRIDGE_PACKAGE_ID,
201            iota_types::bridge::BRIDGE_MODULE_NAME.to_owned(),
202            ident_str!("claim_and_transfer_token").to_owned(),
203            vec![
204                iota_token_type_tags
205                    .get(&token_type)
206                    .ok_or(BridgeError::UnknownTokenId(token_type))?
207                    .clone(),
208            ],
209            vec![arg_bridge, arg_clock, source_chain, seq_num],
210        );
211    }
212
213    let pt = builder.finish();
214
215    Ok(TransactionData::new_programmable(
216        client_address,
217        vec![*gas_object_ref],
218        pt,
219        100_000_000,
220        rgp,
221    ))
222}
223
224fn build_emergency_op_approve_transaction(
225    client_address: IotaAddress,
226    gas_object_ref: &ObjectRef,
227    action: VerifiedCertifiedBridgeAction,
228    bridge_object_arg: ObjectArg,
229    rgp: u64,
230) -> BridgeResult<TransactionData> {
231    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
232
233    let mut builder = ProgrammableTransactionBuilder::new();
234
235    let (source_chain, seq_num, action_type) = match bridge_action {
236        BridgeAction::EmergencyAction(a) => (a.chain_id, a.nonce, a.action_type),
237        _ => unreachable!(),
238    };
239
240    // Unwrap: these should not fail
241    let source_chain = builder.pure(source_chain as u8).unwrap();
242    let seq_num = builder.pure(seq_num).unwrap();
243    let action_type = builder.pure(action_type as u8).unwrap();
244    let arg_bridge = builder.obj(bridge_object_arg).unwrap();
245
246    let arg_msg = builder.programmable_move_call(
247        BRIDGE_PACKAGE_ID,
248        ident_str!("message").to_owned(),
249        ident_str!("create_emergency_op_message").to_owned(),
250        vec![],
251        vec![source_chain, seq_num, action_type],
252    );
253
254    let mut sig_bytes = vec![];
255    for (_, sig) in sigs.signatures {
256        sig_bytes.push(sig.as_bytes().to_vec());
257    }
258    let arg_signatures = builder.pure(sig_bytes.clone()).map_err(|e| {
259        BridgeError::BridgeSerialization(format!(
260            "Failed to serialize signatures: {:?}. Err: {:?}",
261            sig_bytes, e
262        ))
263    })?;
264
265    builder.programmable_move_call(
266        BRIDGE_PACKAGE_ID,
267        ident_str!("bridge").to_owned(),
268        ident_str!("execute_system_message").to_owned(),
269        vec![],
270        vec![arg_bridge, arg_msg, arg_signatures],
271    );
272
273    let pt = builder.finish();
274
275    Ok(TransactionData::new_programmable(
276        client_address,
277        vec![*gas_object_ref],
278        pt,
279        100_000_000,
280        rgp,
281    ))
282}
283
284fn build_committee_blocklist_approve_transaction(
285    client_address: IotaAddress,
286    gas_object_ref: &ObjectRef,
287    action: VerifiedCertifiedBridgeAction,
288    bridge_object_arg: ObjectArg,
289    rgp: u64,
290) -> BridgeResult<TransactionData> {
291    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
292
293    let mut builder = ProgrammableTransactionBuilder::new();
294
295    let (source_chain, seq_num, blocklist_type, members_to_update) = match bridge_action {
296        BridgeAction::BlocklistCommitteeAction(a) => {
297            (a.chain_id, a.nonce, a.blocklist_type, a.members_to_update)
298        }
299        _ => unreachable!(),
300    };
301
302    // Unwrap: these should not fail
303    let source_chain = builder.pure(source_chain as u8).unwrap();
304    let seq_num = builder.pure(seq_num).unwrap();
305    let blocklist_type = builder.pure(blocklist_type as u8).unwrap();
306    let members_to_update = members_to_update
307        .into_iter()
308        .map(|m| m.to_eth_address().as_bytes().to_vec())
309        .collect::<Vec<_>>();
310    let members_to_update = builder.pure(members_to_update).unwrap();
311    let arg_bridge = builder.obj(bridge_object_arg).unwrap();
312
313    let arg_msg = builder.programmable_move_call(
314        BRIDGE_PACKAGE_ID,
315        ident_str!("message").to_owned(),
316        ident_str!("create_blocklist_message").to_owned(),
317        vec![],
318        vec![source_chain, seq_num, blocklist_type, members_to_update],
319    );
320
321    let mut sig_bytes = vec![];
322    for (_, sig) in sigs.signatures {
323        sig_bytes.push(sig.as_bytes().to_vec());
324    }
325    let arg_signatures = builder.pure(sig_bytes.clone()).map_err(|e| {
326        BridgeError::BridgeSerialization(format!(
327            "Failed to serialize signatures: {:?}. Err: {:?}",
328            sig_bytes, e
329        ))
330    })?;
331
332    builder.programmable_move_call(
333        BRIDGE_PACKAGE_ID,
334        ident_str!("bridge").to_owned(),
335        ident_str!("execute_system_message").to_owned(),
336        vec![],
337        vec![arg_bridge, arg_msg, arg_signatures],
338    );
339
340    let pt = builder.finish();
341
342    Ok(TransactionData::new_programmable(
343        client_address,
344        vec![*gas_object_ref],
345        pt,
346        100_000_000,
347        rgp,
348    ))
349}
350
351fn build_limit_update_approve_transaction(
352    client_address: IotaAddress,
353    gas_object_ref: &ObjectRef,
354    action: VerifiedCertifiedBridgeAction,
355    bridge_object_arg: ObjectArg,
356    rgp: u64,
357) -> BridgeResult<TransactionData> {
358    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
359
360    let mut builder = ProgrammableTransactionBuilder::new();
361
362    let (receiving_chain_id, seq_num, sending_chain_id, new_usd_limit) = match bridge_action {
363        BridgeAction::LimitUpdateAction(a) => {
364            (a.chain_id, a.nonce, a.sending_chain_id, a.new_usd_limit)
365        }
366        _ => unreachable!(),
367    };
368
369    // Unwrap: these should not fail
370    let receiving_chain_id = builder.pure(receiving_chain_id as u8).unwrap();
371    let seq_num = builder.pure(seq_num).unwrap();
372    let sending_chain_id = builder.pure(sending_chain_id as u8).unwrap();
373    let new_usd_limit = builder.pure(new_usd_limit).unwrap();
374    let arg_bridge = builder.obj(bridge_object_arg).unwrap();
375
376    let arg_msg = builder.programmable_move_call(
377        BRIDGE_PACKAGE_ID,
378        ident_str!("message").to_owned(),
379        ident_str!("create_update_bridge_limit_message").to_owned(),
380        vec![],
381        vec![receiving_chain_id, seq_num, sending_chain_id, new_usd_limit],
382    );
383
384    let mut sig_bytes = vec![];
385    for (_, sig) in sigs.signatures {
386        sig_bytes.push(sig.as_bytes().to_vec());
387    }
388    let arg_signatures = builder.pure(sig_bytes.clone()).map_err(|e| {
389        BridgeError::BridgeSerialization(format!(
390            "Failed to serialize signatures: {:?}. Err: {:?}",
391            sig_bytes, e
392        ))
393    })?;
394
395    builder.programmable_move_call(
396        BRIDGE_PACKAGE_ID,
397        ident_str!("bridge").to_owned(),
398        ident_str!("execute_system_message").to_owned(),
399        vec![],
400        vec![arg_bridge, arg_msg, arg_signatures],
401    );
402
403    let pt = builder.finish();
404
405    Ok(TransactionData::new_programmable(
406        client_address,
407        vec![*gas_object_ref],
408        pt,
409        100_000_000,
410        rgp,
411    ))
412}
413
414fn build_asset_price_update_approve_transaction(
415    client_address: IotaAddress,
416    gas_object_ref: &ObjectRef,
417    action: VerifiedCertifiedBridgeAction,
418    bridge_object_arg: ObjectArg,
419    rgp: u64,
420) -> BridgeResult<TransactionData> {
421    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
422
423    let mut builder = ProgrammableTransactionBuilder::new();
424
425    let (source_chain, seq_num, token_id, new_usd_price) = match bridge_action {
426        BridgeAction::AssetPriceUpdateAction(a) => {
427            (a.chain_id, a.nonce, a.token_id, a.new_usd_price)
428        }
429        _ => unreachable!(),
430    };
431
432    // Unwrap: these should not fail
433    let source_chain = builder.pure(source_chain as u8).unwrap();
434    let token_id = builder.pure(token_id).unwrap();
435    let seq_num = builder.pure(seq_num).unwrap();
436    let new_price = builder.pure(new_usd_price).unwrap();
437    let arg_bridge = builder.obj(bridge_object_arg).unwrap();
438
439    let arg_msg = builder.programmable_move_call(
440        BRIDGE_PACKAGE_ID,
441        ident_str!("message").to_owned(),
442        ident_str!("create_update_asset_price_message").to_owned(),
443        vec![],
444        vec![token_id, source_chain, seq_num, new_price],
445    );
446
447    let mut sig_bytes = vec![];
448    for (_, sig) in sigs.signatures {
449        sig_bytes.push(sig.as_bytes().to_vec());
450    }
451    let arg_signatures = builder.pure(sig_bytes.clone()).map_err(|e| {
452        BridgeError::BridgeSerialization(format!(
453            "Failed to serialize signatures: {:?}. Err: {:?}",
454            sig_bytes, e
455        ))
456    })?;
457
458    builder.programmable_move_call(
459        BRIDGE_PACKAGE_ID,
460        ident_str!("bridge").to_owned(),
461        ident_str!("execute_system_message").to_owned(),
462        vec![],
463        vec![arg_bridge, arg_msg, arg_signatures],
464    );
465
466    let pt = builder.finish();
467
468    Ok(TransactionData::new_programmable(
469        client_address,
470        vec![*gas_object_ref],
471        pt,
472        100_000_000,
473        rgp,
474    ))
475}
476
477pub fn build_add_tokens_on_iota_transaction(
478    client_address: IotaAddress,
479    gas_object_ref: &ObjectRef,
480    action: VerifiedCertifiedBridgeAction,
481    bridge_object_arg: ObjectArg,
482    rgp: u64,
483) -> BridgeResult<TransactionData> {
484    let (bridge_action, sigs) = action.into_inner().into_data_and_sig();
485
486    let mut builder = ProgrammableTransactionBuilder::new();
487
488    let (source_chain, seq_num, native, token_ids, token_type_names, token_prices) =
489        match bridge_action {
490            BridgeAction::AddTokensOnIotaAction(a) => (
491                a.chain_id,
492                a.nonce,
493                a.native,
494                a.token_ids,
495                a.token_type_names,
496                a.token_prices,
497            ),
498            _ => unreachable!(),
499        };
500    let token_type_names = token_type_names
501        .iter()
502        .map(|type_name| type_name.to_canonical_string(false))
503        .collect::<Vec<_>>();
504    let source_chain = builder.pure(source_chain as u8).unwrap();
505    let seq_num = builder.pure(seq_num).unwrap();
506    let native_token = builder.pure(native).unwrap();
507    let token_ids = builder.pure(token_ids).unwrap();
508    let token_type_names = builder.pure(token_type_names).unwrap();
509    let token_prices = builder.pure(token_prices).unwrap();
510
511    let message_arg = builder.programmable_move_call(
512        BRIDGE_PACKAGE_ID,
513        BRIDGE_MESSAGE_MODULE_NAME.into(),
514        BRIDGE_CREATE_ADD_TOKEN_ON_IOTA_MESSAGE_FUNCTION_NAME.into(),
515        vec![],
516        vec![
517            source_chain,
518            seq_num,
519            native_token,
520            token_ids,
521            token_type_names,
522            token_prices,
523        ],
524    );
525
526    let bridge_arg = builder.obj(bridge_object_arg).unwrap();
527
528    let mut sig_bytes = vec![];
529    for (_, sig) in sigs.signatures {
530        sig_bytes.push(sig.as_bytes().to_vec());
531    }
532    let sigs_arg = builder.pure(sig_bytes.clone()).unwrap();
533
534    builder.programmable_move_call(
535        BRIDGE_PACKAGE_ID,
536        BRIDGE_MODULE_NAME.into(),
537        BRIDGE_EXECUTE_SYSTEM_MESSAGE_FUNCTION_NAME.into(),
538        vec![],
539        vec![bridge_arg, message_arg, sigs_arg],
540    );
541
542    let pt = builder.finish();
543
544    Ok(TransactionData::new_programmable(
545        client_address,
546        vec![*gas_object_ref],
547        pt,
548        100_000_000,
549        rgp,
550    ))
551}
552
553pub fn build_committee_register_transaction(
554    validator_address: IotaAddress,
555    gas_object_ref: &ObjectRef,
556    bridge_object_arg: ObjectArg,
557    bridge_authority_pub_key_bytes: Vec<u8>,
558    bridge_url: &str,
559    ref_gas_price: u64,
560    gas_budget: u64,
561) -> BridgeResult<TransactionData> {
562    let mut builder = ProgrammableTransactionBuilder::new();
563    let system_state = builder.obj(ObjectArg::IOTA_SYSTEM_MUT).unwrap();
564    let bridge = builder.obj(bridge_object_arg).unwrap();
565    let bridge_pubkey = builder
566        .input(CallArg::Pure(
567            bcs::to_bytes(&bridge_authority_pub_key_bytes).unwrap(),
568        ))
569        .unwrap();
570    let url = builder
571        .input(CallArg::Pure(bcs::to_bytes(bridge_url.as_bytes()).unwrap()))
572        .unwrap();
573    builder.programmable_move_call(
574        BRIDGE_PACKAGE_ID,
575        BRIDGE_MODULE_NAME.into(),
576        Identifier::from_str("committee_registration").unwrap(),
577        vec![],
578        vec![bridge, system_state, bridge_pubkey, url],
579    );
580    let data = TransactionData::new_programmable(
581        validator_address,
582        vec![*gas_object_ref],
583        builder.finish(),
584        gas_budget,
585        ref_gas_price,
586    );
587    Ok(data)
588}
589
590pub fn build_committee_update_url_transaction(
591    validator_address: IotaAddress,
592    gas_object_ref: &ObjectRef,
593    bridge_object_arg: ObjectArg,
594    bridge_url: &str,
595    ref_gas_price: u64,
596    gas_budget: u64,
597) -> BridgeResult<TransactionData> {
598    let mut builder = ProgrammableTransactionBuilder::new();
599    let bridge = builder.obj(bridge_object_arg).unwrap();
600    let url = builder
601        .input(CallArg::Pure(bcs::to_bytes(bridge_url.as_bytes()).unwrap()))
602        .unwrap();
603    builder.programmable_move_call(
604        BRIDGE_PACKAGE_ID,
605        BRIDGE_MODULE_NAME.into(),
606        Identifier::from_str("update_node_url").unwrap(),
607        vec![],
608        vec![bridge, url],
609    );
610    let data = TransactionData::new_programmable(
611        validator_address,
612        vec![*gas_object_ref],
613        builder.finish(),
614        gas_budget,
615        ref_gas_price,
616    );
617    Ok(data)
618}
619
620#[cfg(test)]
621mod tests {
622    use std::collections::HashMap;
623
624    use ethers::types::Address as EthAddress;
625    use iota_types::{
626        bridge::{BridgeChainId, TOKEN_ID_BTC, TOKEN_ID_USDC},
627        crypto::{ToFromBytes, get_key_pair},
628    };
629    use test_cluster::TestClusterBuilder;
630
631    use crate::{
632        BRIDGE_ENABLE_PROTOCOL_VERSION,
633        crypto::{BridgeAuthorityKeyPair, BridgeAuthorityPublicKeyBytes},
634        iota_client::IotaClient,
635        test_utils::{
636            approve_action_with_validator_secrets, bridge_token,
637            get_test_eth_to_iota_bridge_action, get_test_iota_to_eth_bridge_action,
638        },
639        types::{BridgeAction, EmergencyAction, EmergencyActionType, *},
640    };
641
642    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
643    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
644    async fn test_build_iota_transaction_for_token_transfer() {
645        telemetry_subscribers::init_for_testing();
646        let mut bridge_keys = vec![];
647        for _ in 0..=3 {
648            let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
649            bridge_keys.push(kp);
650        }
651        let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
652            .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
653            .build_with_bridge(bridge_keys, true)
654            .await;
655
656        let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
657            .await
658            .unwrap();
659        let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
660
661        // Note: We don't call `iota_client.get_bridge_committee` here because it will
662        // err if the committee is not initialized during the construction of
663        // `BridgeCommittee`.
664        test_cluster
665            .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
666            .await;
667        let context = &mut test_cluster.wallet;
668        let sender = context.active_address().unwrap();
669        let usdc_amount = 5000000;
670        let bridge_object_arg = iota_client
671            .get_mutable_bridge_object_arg_must_succeed()
672            .await;
673        let id_token_map = iota_client.get_token_id_map().await.unwrap();
674
675        // 1. Test Eth -> IOTA Transfer approval
676        let action =
677            get_test_eth_to_iota_bridge_action(None, Some(usdc_amount), Some(sender), None);
678        // `approve_action_with_validator_secrets` covers transaction building
679        let usdc_object_ref = approve_action_with_validator_secrets(
680            context,
681            bridge_object_arg,
682            action.clone(),
683            &bridge_authority_keys,
684            Some(sender),
685            &id_token_map,
686        )
687        .await
688        .unwrap();
689
690        // 2. Test IOTA -> Eth Transfer approval
691        let bridge_event = bridge_token(
692            context,
693            EthAddress::random(),
694            usdc_object_ref,
695            id_token_map.get(&TOKEN_ID_USDC).unwrap().clone(),
696            bridge_object_arg,
697        )
698        .await;
699
700        let action = get_test_iota_to_eth_bridge_action(
701            None,
702            None,
703            Some(bridge_event.nonce),
704            Some(bridge_event.amount_iota_adjusted),
705            Some(bridge_event.iota_address),
706            Some(bridge_event.eth_address),
707            Some(TOKEN_ID_USDC),
708        );
709        // `approve_action_with_validator_secrets` covers transaction building
710        approve_action_with_validator_secrets(
711            context,
712            bridge_object_arg,
713            action.clone(),
714            &bridge_authority_keys,
715            None,
716            &id_token_map,
717        )
718        .await;
719    }
720
721    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
722    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
723    async fn test_build_iota_transaction_for_emergency_op() {
724        telemetry_subscribers::init_for_testing();
725        let mut bridge_keys = vec![];
726        for _ in 0..=3 {
727            let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
728            bridge_keys.push(kp);
729        }
730        let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
731            .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
732            .build_with_bridge(bridge_keys, true)
733            .await;
734        let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
735            .await
736            .unwrap();
737        let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
738
739        // Wait until committee is set up
740        test_cluster
741            .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
742            .await;
743        let summary = iota_client.get_bridge_summary().await.unwrap();
744        assert!(!summary.is_frozen);
745
746        let context = &mut test_cluster.wallet;
747        let bridge_object_arg = iota_client
748            .get_mutable_bridge_object_arg_must_succeed()
749            .await;
750        let id_token_map = iota_client.get_token_id_map().await.unwrap();
751
752        // 1. Pause
753        let action = BridgeAction::EmergencyAction(EmergencyAction {
754            nonce: 0,
755            chain_id: BridgeChainId::IotaCustom,
756            action_type: EmergencyActionType::Pause,
757        });
758        // `approve_action_with_validator_secrets` covers transaction building
759        approve_action_with_validator_secrets(
760            context,
761            bridge_object_arg,
762            action.clone(),
763            &bridge_authority_keys,
764            None,
765            &id_token_map,
766        )
767        .await;
768        let summary = iota_client.get_bridge_summary().await.unwrap();
769        assert!(summary.is_frozen);
770
771        // 2. Unpause
772        let action = BridgeAction::EmergencyAction(EmergencyAction {
773            nonce: 1,
774            chain_id: BridgeChainId::IotaCustom,
775            action_type: EmergencyActionType::Unpause,
776        });
777        // `approve_action_with_validator_secrets` covers transaction building
778        approve_action_with_validator_secrets(
779            context,
780            bridge_object_arg,
781            action.clone(),
782            &bridge_authority_keys,
783            None,
784            &id_token_map,
785        )
786        .await;
787        let summary = iota_client.get_bridge_summary().await.unwrap();
788        assert!(!summary.is_frozen);
789    }
790
791    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
792    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
793    async fn test_build_iota_transaction_for_committee_blocklist() {
794        telemetry_subscribers::init_for_testing();
795        let mut bridge_keys = vec![];
796        for _ in 0..=3 {
797            let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
798            bridge_keys.push(kp);
799        }
800        let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
801            .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
802            .build_with_bridge(bridge_keys, true)
803            .await;
804        let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
805            .await
806            .unwrap();
807        let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
808
809        // Wait until committee is set up
810        test_cluster
811            .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
812            .await;
813        let committee = iota_client.get_bridge_summary().await.unwrap().committee;
814        let victim = committee.members.first().unwrap().clone().1;
815        for member in committee.members {
816            assert!(!member.1.blocklisted);
817        }
818
819        let context = &mut test_cluster.wallet;
820        let bridge_object_arg = iota_client
821            .get_mutable_bridge_object_arg_must_succeed()
822            .await;
823        let id_token_map = iota_client.get_token_id_map().await.unwrap();
824
825        // 1. blocklist The victim
826        let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
827            nonce: 0,
828            chain_id: BridgeChainId::IotaCustom,
829            blocklist_type: BlocklistType::Blocklist,
830            members_to_update: vec![
831                BridgeAuthorityPublicKeyBytes::from_bytes(&victim.bridge_pubkey_bytes).unwrap(),
832            ],
833        });
834        // `approve_action_with_validator_secrets` covers transaction building
835        approve_action_with_validator_secrets(
836            context,
837            bridge_object_arg,
838            action.clone(),
839            &bridge_authority_keys,
840            None,
841            &id_token_map,
842        )
843        .await;
844        let committee = iota_client.get_bridge_summary().await.unwrap().committee;
845        for member in committee.members {
846            if member.1.bridge_pubkey_bytes == victim.bridge_pubkey_bytes {
847                assert!(member.1.blocklisted);
848            } else {
849                assert!(!member.1.blocklisted);
850            }
851        }
852
853        // 2. unblocklist the victim
854        let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
855            nonce: 1,
856            chain_id: BridgeChainId::IotaCustom,
857            blocklist_type: BlocklistType::Unblocklist,
858            members_to_update: vec![
859                BridgeAuthorityPublicKeyBytes::from_bytes(&victim.bridge_pubkey_bytes).unwrap(),
860            ],
861        });
862        // `approve_action_with_validator_secrets` covers transaction building
863        approve_action_with_validator_secrets(
864            context,
865            bridge_object_arg,
866            action.clone(),
867            &bridge_authority_keys,
868            None,
869            &id_token_map,
870        )
871        .await;
872        let committee = iota_client.get_bridge_summary().await.unwrap().committee;
873        for member in committee.members {
874            assert!(!member.1.blocklisted);
875        }
876    }
877
878    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
879    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
880    async fn test_build_iota_transaction_for_limit_update() {
881        telemetry_subscribers::init_for_testing();
882        let mut bridge_keys = vec![];
883        for _ in 0..=3 {
884            let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
885            bridge_keys.push(kp);
886        }
887        let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
888            .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
889            .build_with_bridge(bridge_keys, true)
890            .await;
891        let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
892            .await
893            .unwrap();
894        let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
895
896        // Wait until committee is set up
897        test_cluster
898            .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
899            .await;
900        let transfer_limit = iota_client
901            .get_bridge_summary()
902            .await
903            .unwrap()
904            .limiter
905            .transfer_limit
906            .into_iter()
907            .map(|(s, d, l)| ((s, d), l))
908            .collect::<HashMap<_, _>>();
909
910        let context = &mut test_cluster.wallet;
911        let bridge_object_arg = iota_client
912            .get_mutable_bridge_object_arg_must_succeed()
913            .await;
914        let id_token_map = iota_client.get_token_id_map().await.unwrap();
915
916        // update limit
917        let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
918            nonce: 0,
919            chain_id: BridgeChainId::IotaCustom,
920            sending_chain_id: BridgeChainId::EthCustom,
921            new_usd_limit: 6_666_666 * USD_MULTIPLIER, // $1M USD
922        });
923        // `approve_action_with_validator_secrets` covers transaction building
924        approve_action_with_validator_secrets(
925            context,
926            bridge_object_arg,
927            action.clone(),
928            &bridge_authority_keys,
929            None,
930            &id_token_map,
931        )
932        .await;
933        let new_transfer_limit = iota_client
934            .get_bridge_summary()
935            .await
936            .unwrap()
937            .limiter
938            .transfer_limit;
939        for limit in new_transfer_limit {
940            if limit.0 == BridgeChainId::EthCustom && limit.1 == BridgeChainId::IotaCustom {
941                assert_eq!(limit.2, 6_666_666 * USD_MULTIPLIER);
942            } else {
943                assert_eq!(limit.2, *transfer_limit.get(&(limit.0, limit.1)).unwrap());
944            }
945        }
946    }
947
948    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
949    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
950    async fn test_build_iota_transaction_for_price_update() {
951        telemetry_subscribers::init_for_testing();
952        let mut bridge_keys = vec![];
953        for _ in 0..=3 {
954            let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
955            bridge_keys.push(kp);
956        }
957        let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new()
958            .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into())
959            .build_with_bridge(bridge_keys, true)
960            .await;
961        let iota_client = IotaClient::new(&test_cluster.fullnode_handle.rpc_url)
962            .await
963            .unwrap();
964        let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap();
965
966        // Note: We don't call `iota_client.get_bridge_committee` here because it will
967        // err if the committee is not initialized during the construction of
968        // `BridgeCommittee`.
969        test_cluster
970            .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized()
971            .await;
972        let notional_values = iota_client.get_notional_values().await.unwrap();
973        assert_ne!(notional_values[&TOKEN_ID_USDC], 69_000 * USD_MULTIPLIER);
974
975        let context = &mut test_cluster.wallet;
976        let bridge_object_arg = iota_client
977            .get_mutable_bridge_object_arg_must_succeed()
978            .await;
979        let id_token_map = iota_client.get_token_id_map().await.unwrap();
980
981        // update price
982        let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
983            nonce: 0,
984            chain_id: BridgeChainId::IotaCustom,
985            token_id: TOKEN_ID_BTC,
986            new_usd_price: 69_000 * USD_MULTIPLIER, // $69k USD
987        });
988        // `approve_action_with_validator_secrets` covers transaction building
989        approve_action_with_validator_secrets(
990            context,
991            bridge_object_arg,
992            action.clone(),
993            &bridge_authority_keys,
994            None,
995            &id_token_map,
996        )
997        .await;
998        let new_notional_values = iota_client.get_notional_values().await.unwrap();
999        for (token_id, price) in new_notional_values {
1000            if token_id == TOKEN_ID_BTC {
1001                assert_eq!(price, 69_000 * USD_MULTIPLIER);
1002            } else {
1003                assert_eq!(price, *notional_values.get(&token_id).unwrap());
1004            }
1005        }
1006    }
1007}