1use 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 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 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 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 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 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 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 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 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 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 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 let action = get_test_eth_to_sui_bridge_action(None, Some(usdc_amount), Some(sender), None);
751 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 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(
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 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 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(
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 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(
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 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 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(
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 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(
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 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 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, });
1000 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 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 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, });
1065 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}