sui_bridge/
sui_transaction_builder.rs

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