sui_bridge/
events.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This file contains the definition of the SuiBridgeEvent enum, of
5//! which each variant is an emitted Event struct defind in the Move
6//! Bridge module. We rely on structures in this file to decode
7//! the bcs content of the emitted events.
8
9#![allow(non_upper_case_globals)]
10
11use crate::crypto::BridgeAuthorityPublicKey;
12use crate::error::BridgeError;
13use crate::error::BridgeResult;
14use crate::types::BridgeAction;
15use crate::types::SuiToEthTokenTransfer;
16use crate::types::SuiToEthTokenTransferV2;
17use alloy::primitives::Address as EthAddress;
18use fastcrypto::encoding::Encoding;
19use fastcrypto::encoding::Hex;
20use move_core_types::language_storage::StructTag;
21use once_cell::sync::OnceCell;
22use serde::{Deserialize, Serialize};
23use std::str::FromStr;
24use sui_json_rpc_types::SuiEvent;
25use sui_types::BRIDGE_PACKAGE_ID;
26use sui_types::TypeTag;
27use sui_types::base_types::SuiAddress;
28use sui_types::bridge::BridgeChainId;
29use sui_types::bridge::MoveTypeBridgeMessageKey;
30use sui_types::bridge::MoveTypeCommitteeMember;
31use sui_types::bridge::MoveTypeCommitteeMemberRegistration;
32use sui_types::collection_types::VecMap;
33use sui_types::crypto::ToFromBytes;
34use sui_types::parse_sui_type_tag;
35
36// `TokendDepositedEvent` emitted in bridge.move
37#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
38pub struct MoveTokenDepositedEvent {
39    pub seq_num: u64,
40    pub source_chain: u8,
41    pub sender_address: Vec<u8>,
42    pub target_chain: u8,
43    pub target_address: Vec<u8>,
44    pub token_type: u8,
45    pub amount_sui_adjusted: u64,
46}
47
48// `TokendDepositedEventV2` emitted in bridge.move
49#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
50pub struct MoveTokenDepositedEventV2 {
51    pub seq_num: u64,
52    pub source_chain: u8,
53    pub sender_address: Vec<u8>,
54    pub target_chain: u8,
55    pub target_address: Vec<u8>,
56    pub token_type: u8,
57    pub amount_sui_adjusted: u64,
58    pub timestamp_ms: u64,
59}
60
61macro_rules! new_move_event {
62    ($struct_name:ident, $move_struct_name:ident) => {
63
64        // `$move_struct_name` emitted in bridge.move
65        #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
66        pub struct $move_struct_name {
67            pub message_key: MoveTypeBridgeMessageKey,
68        }
69
70        // Sanitized version of the given `move_struct_name`
71        #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
72        pub struct $struct_name {
73            pub nonce: u64,
74            pub source_chain: BridgeChainId,
75        }
76
77        impl TryFrom<$move_struct_name> for $struct_name {
78            type Error = BridgeError;
79
80            fn try_from(event: $move_struct_name) -> BridgeResult<Self> {
81                let source_chain = BridgeChainId::try_from(event.message_key.source_chain).map_err(|_e| {
82                    BridgeError::Generic(format!(
83                        "Failed to convert {} to {}. Failed to convert source chain {} to BridgeChainId",
84                        stringify!($move_struct_name),
85                        stringify!($struct_name),
86                        event.message_key.source_chain,
87                    ))
88                })?;
89                Ok(Self {
90                    nonce: event.message_key.bridge_seq_num,
91                    source_chain,
92                })
93            }
94        }
95    };
96}
97
98new_move_event!(TokenTransferClaimed, MoveTokenTransferClaimed);
99new_move_event!(TokenTransferApproved, MoveTokenTransferApproved);
100new_move_event!(
101    TokenTransferAlreadyApproved,
102    MoveTokenTransferAlreadyApproved
103);
104new_move_event!(TokenTransferAlreadyClaimed, MoveTokenTransferAlreadyClaimed);
105new_move_event!(TokenTransferLimitExceed, MoveTokenTransferLimitExceed);
106
107// `EmergencyOpEvent` emitted in bridge.move
108#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
109pub struct EmergencyOpEvent {
110    pub frozen: bool,
111}
112
113// `CommitteeUpdateEvent` emitted in committee.move
114#[derive(Debug, Serialize, Deserialize, Clone)]
115pub struct MoveCommitteeUpdateEvent {
116    pub members: VecMap<Vec<u8>, MoveTypeCommitteeMember>,
117    pub stake_participation_percentage: u64,
118}
119
120// `CommitteeMemberUrlUpdateEvent` emitted in committee.move
121#[derive(Debug, Serialize, Deserialize, Clone)]
122pub struct MoveCommitteeMemberUrlUpdateEvent {
123    pub member: Vec<u8>,
124    pub new_url: Vec<u8>,
125}
126
127// `BlocklistValidatorEvent` emitted in committee.move
128#[derive(Debug, Serialize, Deserialize, Clone)]
129pub struct MoveBlocklistValidatorEvent {
130    pub blocklisted: bool,
131    pub public_keys: Vec<Vec<u8>>,
132}
133
134// `UpdateRouteLimitEvent` emitted in limiter.move
135#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
136pub struct UpdateRouteLimitEvent {
137    pub sending_chain: u8,
138    pub receiving_chain: u8,
139    pub new_limit: u64,
140}
141
142// `TokenRegistrationEvent` emitted in treasury.move
143#[derive(Debug, Serialize, Deserialize, Clone)]
144pub struct MoveTokenRegistrationEvent {
145    pub type_name: String,
146    pub decimal: u8,
147    pub native_token: bool,
148}
149
150// Sanitized version of MoveTokenRegistrationEvent
151#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
152pub struct TokenRegistrationEvent {
153    pub type_name: TypeTag,
154    pub decimal: u8,
155    pub native_token: bool,
156}
157
158impl TryFrom<MoveTokenRegistrationEvent> for TokenRegistrationEvent {
159    type Error = BridgeError;
160
161    fn try_from(event: MoveTokenRegistrationEvent) -> BridgeResult<Self> {
162        let type_name = parse_sui_type_tag(&format!("0x{}", event.type_name)).map_err(|e| {
163            BridgeError::InternalError(format!(
164                "Failed to parse TypeTag: {e}, type name: {}",
165                event.type_name
166            ))
167        })?;
168
169        Ok(Self {
170            type_name,
171            decimal: event.decimal,
172            native_token: event.native_token,
173        })
174    }
175}
176
177// `NewTokenEvent` emitted in treasury.move
178#[derive(Debug, Serialize, Deserialize, Clone)]
179pub struct MoveNewTokenEvent {
180    pub token_id: u8,
181    pub type_name: String,
182    pub native_token: bool,
183    pub decimal_multiplier: u64,
184    pub notional_value: u64,
185}
186
187// Sanitized version of MoveNewTokenEvent
188#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
189pub struct NewTokenEvent {
190    pub token_id: u8,
191    pub type_name: TypeTag,
192    pub native_token: bool,
193    pub decimal_multiplier: u64,
194    pub notional_value: u64,
195}
196
197impl TryFrom<MoveNewTokenEvent> for NewTokenEvent {
198    type Error = BridgeError;
199
200    fn try_from(event: MoveNewTokenEvent) -> BridgeResult<Self> {
201        let type_name = parse_sui_type_tag(&format!("0x{}", event.type_name)).map_err(|e| {
202            BridgeError::InternalError(format!(
203                "Failed to parse TypeTag: {e}, type name: {}",
204                event.type_name
205            ))
206        })?;
207
208        Ok(Self {
209            token_id: event.token_id,
210            type_name,
211            native_token: event.native_token,
212            decimal_multiplier: event.decimal_multiplier,
213            notional_value: event.notional_value,
214        })
215    }
216}
217
218// `UpdateTokenPriceEvent` emitted in treasury.move
219#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
220pub struct UpdateTokenPriceEvent {
221    pub token_id: u8,
222    pub new_price: u64,
223}
224
225// Sanitized version of MoveTokenDepositedEvent
226#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
227pub struct EmittedSuiToEthTokenBridgeV1 {
228    pub nonce: u64,
229    pub sui_chain_id: BridgeChainId,
230    pub eth_chain_id: BridgeChainId,
231    pub sui_address: SuiAddress,
232    pub eth_address: EthAddress,
233    pub token_id: u8,
234    // The amount of tokens deposited with decimal points on Sui side
235    pub amount_sui_adjusted: u64,
236}
237
238// Sanitized version of MoveTokenDepositedEventV2
239#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
240pub struct EmittedSuiToEthTokenBridgeV2 {
241    pub nonce: u64,
242    pub sui_chain_id: BridgeChainId,
243    pub eth_chain_id: BridgeChainId,
244    pub sui_address: SuiAddress,
245    pub eth_address: EthAddress,
246    pub token_id: u8,
247    // The amount of tokens deposited with decimal points on Sui side
248    pub amount_sui_adjusted: u64,
249    pub timestamp_ms: u64,
250}
251
252// Sanitized version of MoveCommitteeUpdateEvent
253#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
254pub struct CommitteeUpdate {
255    pub members: Vec<MoveTypeCommitteeMember>,
256    pub stake_participation_percentage: u64,
257}
258
259impl TryFrom<MoveCommitteeUpdateEvent> for CommitteeUpdate {
260    type Error = BridgeError;
261
262    fn try_from(event: MoveCommitteeUpdateEvent) -> BridgeResult<Self> {
263        let members = event
264            .members
265            .contents
266            .into_iter()
267            .map(|v| v.value)
268            .collect();
269        Ok(Self {
270            members,
271            stake_participation_percentage: event.stake_participation_percentage,
272        })
273    }
274}
275
276// Sanitized version of MoveBlocklistValidatorEvent
277#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
278pub struct BlocklistValidatorEvent {
279    pub blocklisted: bool,
280    pub public_keys: Vec<BridgeAuthorityPublicKey>,
281}
282
283impl TryFrom<MoveBlocklistValidatorEvent> for BlocklistValidatorEvent {
284    type Error = BridgeError;
285
286    fn try_from(event: MoveBlocklistValidatorEvent) -> BridgeResult<Self> {
287        let public_keys = event.public_keys.into_iter().map(|bytes|
288            BridgeAuthorityPublicKey::from_bytes(&bytes).map_err(|e|
289                BridgeError::Generic(format!("Failed to convert MoveBlocklistValidatorEvent to BlocklistValidatorEvent. Failed to convert public key to BridgeAuthorityPublicKey: {:?}", e))
290            )
291        ).collect::<BridgeResult<Vec<_>>>()?;
292        Ok(Self {
293            blocklisted: event.blocklisted,
294            public_keys,
295        })
296    }
297}
298
299// Sanitized version of MoveCommitteeMemberUrlUpdateEvent
300#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
301pub struct CommitteeMemberUrlUpdateEvent {
302    pub member: BridgeAuthorityPublicKey,
303    pub new_url: String,
304}
305
306impl TryFrom<MoveCommitteeMemberUrlUpdateEvent> for CommitteeMemberUrlUpdateEvent {
307    type Error = BridgeError;
308
309    fn try_from(event: MoveCommitteeMemberUrlUpdateEvent) -> BridgeResult<Self> {
310        let member = BridgeAuthorityPublicKey::from_bytes(&event.member).map_err(|e|
311            BridgeError::Generic(format!("Failed to convert MoveBlocklistValidatorEvent to BlocklistValidatorEvent. Failed to convert public key to BridgeAuthorityPublicKey: {:?}", e))
312        )?;
313        let new_url = String::from_utf8(event.new_url).map_err(|e|
314            BridgeError::Generic(format!("Failed to convert MoveBlocklistValidatorEvent to BlocklistValidatorEvent. Failed to convert new_url to String: {:?}", e))
315        )?;
316        Ok(Self { member, new_url })
317    }
318}
319
320impl TryFrom<MoveTokenDepositedEvent> for EmittedSuiToEthTokenBridgeV1 {
321    type Error = BridgeError;
322
323    fn try_from(event: MoveTokenDepositedEvent) -> BridgeResult<Self> {
324        if event.amount_sui_adjusted == 0 {
325            return Err(BridgeError::ZeroValueBridgeTransfer(format!(
326                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Manual intervention is required. 0 value transfer should not be allowed in Move: {:?}",
327                event,
328            )));
329        }
330
331        let token_id = event.token_type;
332        let sui_chain_id = BridgeChainId::try_from(event.source_chain).map_err(|_e| {
333            BridgeError::Generic(format!(
334                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert source chain {} to BridgeChainId",
335                event.token_type,
336            ))
337        })?;
338        let eth_chain_id = BridgeChainId::try_from(event.target_chain).map_err(|_e| {
339            BridgeError::Generic(format!(
340                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert target chain {} to BridgeChainId",
341                event.token_type,
342            ))
343        })?;
344        if !sui_chain_id.is_sui_chain() {
345            return Err(BridgeError::Generic(format!(
346                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Invalid source chain {}",
347                event.source_chain
348            )));
349        }
350        if eth_chain_id.is_sui_chain() {
351            return Err(BridgeError::Generic(format!(
352                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Invalid target chain {}",
353                event.target_chain
354            )));
355        }
356
357        let sui_address = SuiAddress::from_bytes(event.sender_address)
358            .map_err(|e| BridgeError::Generic(format!("Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert sender_address to SuiAddress: {:?}", e)))?;
359        let eth_address = EthAddress::from_str(&Hex::encode(&event.target_address))?;
360
361        Ok(Self {
362            nonce: event.seq_num,
363            sui_chain_id,
364            eth_chain_id,
365            sui_address,
366            eth_address,
367            token_id,
368            amount_sui_adjusted: event.amount_sui_adjusted,
369        })
370    }
371}
372
373impl TryFrom<MoveTokenDepositedEventV2> for EmittedSuiToEthTokenBridgeV2 {
374    type Error = BridgeError;
375
376    fn try_from(event: MoveTokenDepositedEventV2) -> BridgeResult<Self> {
377        if event.amount_sui_adjusted == 0 {
378            return Err(BridgeError::ZeroValueBridgeTransfer(format!(
379                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Manual intervention is required. 0 value transfer should not be allowed in Move: {:?}",
380                event,
381            )));
382        }
383
384        let token_id = event.token_type;
385        let sui_chain_id = BridgeChainId::try_from(event.source_chain).map_err(|_e| {
386            BridgeError::Generic(format!(
387                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert source chain {} to BridgeChainId",
388                event.token_type,
389            ))
390        })?;
391        let eth_chain_id = BridgeChainId::try_from(event.target_chain).map_err(|_e| {
392            BridgeError::Generic(format!(
393                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert target chain {} to BridgeChainId",
394                event.token_type,
395            ))
396        })?;
397        if !sui_chain_id.is_sui_chain() {
398            return Err(BridgeError::Generic(format!(
399                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Invalid source chain {}",
400                event.source_chain
401            )));
402        }
403        if eth_chain_id.is_sui_chain() {
404            return Err(BridgeError::Generic(format!(
405                "Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Invalid target chain {}",
406                event.target_chain
407            )));
408        }
409
410        let sui_address = SuiAddress::from_bytes(event.sender_address)
411            .map_err(|e| BridgeError::Generic(format!("Failed to convert MoveTokenDepositedEvent to EmittedSuiToEthTokenBridgeV1. Failed to convert sender_address to SuiAddress: {:?}", e)))?;
412        let eth_address = EthAddress::from_str(&Hex::encode(&event.target_address))?;
413
414        Ok(Self {
415            nonce: event.seq_num,
416            sui_chain_id,
417            eth_chain_id,
418            sui_address,
419            eth_address,
420            token_id,
421            amount_sui_adjusted: event.amount_sui_adjusted,
422            timestamp_ms: event.timestamp_ms,
423        })
424    }
425}
426
427crate::declare_events!(
428    SuiToEthTokenBridgeV1(EmittedSuiToEthTokenBridgeV1) => ("bridge::TokenDepositedEvent", MoveTokenDepositedEvent),
429    TokenTransferApproved(TokenTransferApproved) => ("bridge::TokenTransferApproved", MoveTokenTransferApproved),
430    TokenTransferClaimed(TokenTransferClaimed) => ("bridge::TokenTransferClaimed", MoveTokenTransferClaimed),
431    TokenTransferAlreadyApproved(TokenTransferAlreadyApproved) => ("bridge::TokenTransferAlreadyApproved", MoveTokenTransferAlreadyApproved),
432    TokenTransferAlreadyClaimed(TokenTransferAlreadyClaimed) => ("bridge::TokenTransferAlreadyClaimed", MoveTokenTransferAlreadyClaimed),
433    TokenTransferLimitExceed(TokenTransferLimitExceed) => ("bridge::TokenTransferLimitExceed", MoveTokenTransferLimitExceed),
434    EmergencyOpEvent(EmergencyOpEvent) => ("bridge::EmergencyOpEvent", EmergencyOpEvent),
435    // No need to define a sanitized event struct for MoveTypeCommitteeMemberRegistration
436    // because the info provided by validators could be invalid
437    CommitteeMemberRegistration(MoveTypeCommitteeMemberRegistration) => ("committee::CommitteeMemberRegistration", MoveTypeCommitteeMemberRegistration),
438    CommitteeUpdateEvent(CommitteeUpdate) => ("committee::CommitteeUpdateEvent", MoveCommitteeUpdateEvent),
439    CommitteeMemberUrlUpdateEvent(CommitteeMemberUrlUpdateEvent) => ("committee::CommitteeMemberUrlUpdateEvent", MoveCommitteeMemberUrlUpdateEvent),
440    BlocklistValidatorEvent(BlocklistValidatorEvent) => ("committee::BlocklistValidatorEvent", MoveBlocklistValidatorEvent),
441    TokenRegistrationEvent(TokenRegistrationEvent) => ("treasury::TokenRegistrationEvent", MoveTokenRegistrationEvent),
442    NewTokenEvent(NewTokenEvent) => ("treasury::NewTokenEvent", MoveNewTokenEvent),
443    UpdateTokenPriceEvent(UpdateTokenPriceEvent) => ("treasury::UpdateTokenPriceEvent", UpdateTokenPriceEvent),
444    UpdateRouteLimitEvent(UpdateRouteLimitEvent) => ("limiter::UpdateRouteLimitEvent", UpdateRouteLimitEvent),
445    SuiToEthTokenBridgeV2(EmittedSuiToEthTokenBridgeV2) => ("bridge::TokenDepositedEventV2", MoveTokenDepositedEventV2),
446
447    // Add new event types here. Format:
448    // EnumVariantName(Struct) => ("{module}::{event_struct}", CorrespondingMoveStruct)
449);
450
451#[macro_export]
452macro_rules! declare_events {
453    ($($variant:ident($type:path) => ($event_tag:expr, $event_struct:path)),* $(,)?) => {
454
455        #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
456        pub enum SuiBridgeEvent {
457            $($variant($type),)*
458        }
459
460        $(pub static $variant: OnceCell<StructTag> = OnceCell::new();)*
461
462        pub(crate) fn init_all_struct_tags() {
463            $($variant.get_or_init(|| {
464                StructTag::from_str(&format!("0x{}::{}", BRIDGE_PACKAGE_ID.to_hex(), $event_tag)).unwrap()
465            });)*
466        }
467
468        // Try to convert a SuiEvent into SuiBridgeEvent
469        impl SuiBridgeEvent {
470            pub fn try_from_sui_event(event: &SuiEvent) -> BridgeResult<Option<SuiBridgeEvent>> {
471                init_all_struct_tags(); // Ensure all tags are initialized
472
473                // Unwrap safe: we inited above
474                $(
475                    if &event.type_ == $variant.get().unwrap() {
476                        let event_struct: $event_struct = bcs::from_bytes(event.bcs.bytes()).map_err(|e| BridgeError::InternalError(format!("Failed to deserialize event to {}: {:?}", stringify!($event_struct), e)))?;
477                        return Ok(Some(SuiBridgeEvent::$variant(event_struct.try_into()?)));
478                    }
479                )*
480                Ok(None)
481            }
482        }
483    };
484}
485
486impl SuiBridgeEvent {
487    pub fn try_into_bridge_action(self) -> Option<BridgeAction> {
488        match self {
489            SuiBridgeEvent::SuiToEthTokenBridgeV1(event) => {
490                let EmittedSuiToEthTokenBridgeV1 {
491                    nonce,
492                    sui_chain_id,
493                    eth_chain_id,
494                    sui_address,
495                    eth_address,
496                    token_id,
497                    amount_sui_adjusted,
498                } = event;
499
500                Some(BridgeAction::SuiToEthTokenTransfer(SuiToEthTokenTransfer {
501                    nonce,
502                    sui_chain_id,
503                    eth_chain_id,
504                    sui_address,
505                    eth_address,
506                    token_id,
507                    amount_adjusted: amount_sui_adjusted,
508                }))
509            }
510            SuiBridgeEvent::SuiToEthTokenBridgeV2(event) => Some(
511                BridgeAction::SuiToEthTokenTransferV2(SuiToEthTokenTransferV2 {
512                    nonce: event.nonce,
513                    sui_chain_id: event.sui_chain_id,
514                    eth_chain_id: event.eth_chain_id,
515                    sui_address: event.sui_address,
516                    eth_address: event.eth_address,
517                    token_id: event.token_id,
518                    amount_adjusted: event.amount_sui_adjusted,
519                    timestamp_ms: event.timestamp_ms,
520                }),
521            ),
522            SuiBridgeEvent::TokenTransferApproved(_event) => None,
523            SuiBridgeEvent::TokenTransferClaimed(_event) => None,
524            SuiBridgeEvent::TokenTransferAlreadyApproved(_event) => None,
525            SuiBridgeEvent::TokenTransferAlreadyClaimed(_event) => None,
526            SuiBridgeEvent::TokenTransferLimitExceed(_event) => None,
527            SuiBridgeEvent::EmergencyOpEvent(_event) => None,
528            SuiBridgeEvent::CommitteeMemberRegistration(_event) => None,
529            SuiBridgeEvent::CommitteeUpdateEvent(_event) => None,
530            SuiBridgeEvent::CommitteeMemberUrlUpdateEvent(_event) => None,
531            SuiBridgeEvent::BlocklistValidatorEvent(_event) => None,
532            SuiBridgeEvent::TokenRegistrationEvent(_event) => None,
533            SuiBridgeEvent::NewTokenEvent(_event) => None,
534            SuiBridgeEvent::UpdateTokenPriceEvent(_event) => None,
535            SuiBridgeEvent::UpdateRouteLimitEvent(_event) => None,
536        }
537    }
538}
539
540#[cfg(test)]
541pub mod tests {
542    use std::collections::HashSet;
543
544    use super::*;
545    use crate::crypto::BridgeAuthorityKeyPair;
546    use crate::e2e_tests::test_utils::BridgeTestClusterBuilder;
547    use crate::types::BridgeAction;
548    use crate::types::SuiToEthBridgeAction;
549    use alloy::primitives::Address as EthAddress;
550    use sui_json_rpc_types::BcsEvent;
551    use sui_json_rpc_types::SuiEvent;
552    use sui_types::Identifier;
553    use sui_types::base_types::ObjectID;
554    use sui_types::base_types::SuiAddress;
555    use sui_types::bridge::BridgeChainId;
556    use sui_types::bridge::TOKEN_ID_SUI;
557    use sui_types::crypto::get_key_pair;
558    use sui_types::digests::TransactionDigest;
559    use sui_types::event::EventID;
560
561    /// Returns a test SuiEvent and corresponding BridgeAction
562    pub fn get_test_sui_event_and_action(identifier: Identifier) -> (SuiEvent, BridgeAction) {
563        init_all_struct_tags(); // Ensure all tags are initialized
564        let sanitized_event = EmittedSuiToEthTokenBridgeV1 {
565            nonce: 1,
566            sui_chain_id: BridgeChainId::SuiTestnet,
567            sui_address: SuiAddress::random_for_testing_only(),
568            eth_chain_id: BridgeChainId::EthSepolia,
569            eth_address: EthAddress::random(),
570            token_id: TOKEN_ID_SUI,
571            amount_sui_adjusted: 100,
572        };
573        let emitted_event = MoveTokenDepositedEvent {
574            seq_num: sanitized_event.nonce,
575            source_chain: sanitized_event.sui_chain_id as u8,
576            sender_address: sanitized_event.sui_address.to_vec(),
577            target_chain: sanitized_event.eth_chain_id as u8,
578            target_address: sanitized_event.eth_address.to_vec(),
579            token_type: sanitized_event.token_id,
580            amount_sui_adjusted: sanitized_event.amount_sui_adjusted,
581        };
582
583        let tx_digest = TransactionDigest::random();
584        let event_idx = 10u16;
585        let bridge_action = BridgeAction::SuiToEthBridgeAction(SuiToEthBridgeAction {
586            sui_tx_digest: tx_digest,
587            sui_tx_event_index: event_idx,
588            sui_bridge_event: sanitized_event.clone(),
589        });
590        let event = SuiEvent {
591            type_: SuiToEthTokenBridgeV1.get().unwrap().clone(),
592            bcs: BcsEvent::new(bcs::to_bytes(&emitted_event).unwrap()),
593            id: EventID {
594                tx_digest,
595                event_seq: event_idx as u64,
596            },
597
598            // The following fields do not matter as of writing,
599            // but if tests start to fail, it's worth checking these fields.
600            package_id: ObjectID::ZERO,
601            transaction_module: identifier.clone(),
602            sender: SuiAddress::random_for_testing_only(),
603            parsed_json: serde_json::json!({"test": "test"}),
604            timestamp_ms: None,
605        };
606        (event, bridge_action)
607    }
608
609    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
610    async fn test_bridge_events_when_init() {
611        telemetry_subscribers::init_for_testing();
612        init_all_struct_tags();
613        let mut bridge_test_cluster = BridgeTestClusterBuilder::new()
614            .with_eth_env(false)
615            .with_bridge_cluster(false)
616            .with_num_validators(2)
617            .build()
618            .await;
619
620        let events = bridge_test_cluster
621            .new_bridge_events(
622                HashSet::from_iter([
623                    CommitteeMemberRegistration.get().unwrap().clone(),
624                    CommitteeUpdateEvent.get().unwrap().clone(),
625                    TokenRegistrationEvent.get().unwrap().clone(),
626                    NewTokenEvent.get().unwrap().clone(),
627                ]),
628                false,
629            )
630            .await;
631        let mut mask = 0u8;
632        for event in events.iter() {
633            match SuiBridgeEvent::try_from_sui_event(event).unwrap().unwrap() {
634                SuiBridgeEvent::CommitteeMemberRegistration(_event) => mask |= 0x1,
635                SuiBridgeEvent::CommitteeUpdateEvent(_event) => mask |= 0x2,
636                SuiBridgeEvent::TokenRegistrationEvent(_event) => mask |= 0x4,
637                SuiBridgeEvent::NewTokenEvent(_event) => mask |= 0x8,
638                _ => panic!("Got unexpected event: {:?}", event),
639            }
640        }
641        // assert all the above events are emitted
642        assert_eq!(mask, 0xF);
643
644        // TODO: trigger other events and make sure they are converted correctly
645    }
646
647    #[test]
648    fn test_conversion_for_committee_member_url_update_event() {
649        let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
650        let new_url = "https://example.com:443";
651        let event: CommitteeMemberUrlUpdateEvent = MoveCommitteeMemberUrlUpdateEvent {
652            member: kp.public.as_bytes().to_vec(),
653            new_url: new_url.as_bytes().to_vec(),
654        }
655        .try_into()
656        .unwrap();
657        assert_eq!(event.member, kp.public);
658        assert_eq!(event.new_url, new_url);
659
660        CommitteeMemberUrlUpdateEvent::try_from(MoveCommitteeMemberUrlUpdateEvent {
661            member: vec![1, 2, 3],
662            new_url: new_url.as_bytes().to_vec(),
663        })
664        .unwrap_err();
665
666        CommitteeMemberUrlUpdateEvent::try_from(MoveCommitteeMemberUrlUpdateEvent {
667            member: kp.public.as_bytes().to_vec(),
668            new_url: [240, 130, 130, 172].into(),
669        })
670        .unwrap_err();
671    }
672
673    // TODO: add conversion tests for other events
674
675    #[test]
676    fn test_0_sui_amount_conversion_for_sui_event() {
677        let emitted_event = MoveTokenDepositedEvent {
678            seq_num: 1,
679            source_chain: BridgeChainId::SuiTestnet as u8,
680            sender_address: SuiAddress::random_for_testing_only().to_vec(),
681            target_chain: BridgeChainId::EthSepolia as u8,
682            target_address: EthAddress::random().to_vec(),
683            token_type: TOKEN_ID_SUI,
684            amount_sui_adjusted: 0,
685        };
686        match EmittedSuiToEthTokenBridgeV1::try_from(emitted_event).unwrap_err() {
687            BridgeError::ZeroValueBridgeTransfer(_) => (),
688            other => panic!("Expected Generic error, got: {:?}", other),
689        }
690    }
691}