iota_bridge/
events.rs

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