1use crate::encoding::{
5    ADD_TOKENS_ON_EVM_MESSAGE_VERSION, ASSET_PRICE_UPDATE_MESSAGE_VERSION, BridgeMessageEncoding,
6    EVM_CONTRACT_UPGRADE_MESSAGE_VERSION, LIMIT_UPDATE_MESSAGE_VERSION,
7};
8use crate::encoding::{
9    COMMITTEE_BLOCKLIST_MESSAGE_VERSION, EMERGENCY_BUTTON_MESSAGE_VERSION,
10    TOKEN_TRANSFER_MESSAGE_VERSION,
11};
12use crate::error::{BridgeError, BridgeResult};
13use crate::types::ParsedTokenTransferMessage;
14use crate::types::{
15    AddTokensOnEvmAction, AssetPriceUpdateAction, BlocklistCommitteeAction, BridgeAction,
16    BridgeActionType, EmergencyAction, EthLog, EthToSuiBridgeAction, EvmContractUpgradeAction,
17    LimitUpdateAction, SuiToEthBridgeAction,
18};
19use ethers::types::Log;
20use ethers::{
21    abi::RawLog,
22    contract::{EthLogDecode, abigen},
23    types::Address as EthAddress,
24};
25use serde::{Deserialize, Serialize};
26use sui_types::base_types::SuiAddress;
27use sui_types::bridge::BridgeChainId;
28
29macro_rules! gen_eth_events {
30    ($($contract:ident, $contract_event:ident, $abi_path:literal),* $(,)?) => {
31        $(
32            abigen!(
33                $contract,
34                $abi_path,
35                event_derives(serde::Deserialize, serde::Serialize)
36            );
37        )*
38
39        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40        pub enum EthBridgeEvent {
41            $(
42                $contract_event($contract_event),
43            )*
44        }
45
46        impl EthBridgeEvent {
47            pub fn try_from_eth_log(log: &EthLog) -> Option<EthBridgeEvent> {
48                Self::try_from_log(&log.log)
49            }
50
51            pub fn try_from_log(log: &Log) -> Option<EthBridgeEvent> {
52                let raw_log = RawLog {
53                    topics: log.topics.clone(),
54                    data: log.data.to_vec(),
55                };
56
57                $(
58                    if let Ok(decoded) = $contract_event::decode_log(&raw_log) {
59                        return Some(EthBridgeEvent::$contract_event(decoded));
60                    }
61                )*
62
63                None
64            }
65        }
66    };
67
68    ($($contract:ident, $abi_path:literal),* $(,)?) => {
70        $(
71            abigen!(
72                $contract,
73                $abi_path,
74                event_derives(serde::Deserialize, serde::Serialize)
75            );
76        )*
77    };
78}
79
80#[rustfmt::skip]
81gen_eth_events!(
82    EthSuiBridge, EthSuiBridgeEvents, "abi/sui_bridge.json",
83    EthBridgeCommittee, EthBridgeCommitteeEvents, "abi/bridge_committee.json",
84    EthBridgeLimiter, EthBridgeLimiterEvents, "abi/bridge_limiter.json",
85    EthBridgeConfig, EthBridgeConfigEvents, "abi/bridge_config.json",
86    EthCommitteeUpgradeableContract, EthCommitteeUpgradeableContractEvents, "abi/bridge_committee_upgradeable.json"
87);
88
89gen_eth_events!(EthBridgeVault, "abi/bridge_vault.json");
90
91abigen!(
92    EthERC20,
93    "abi/erc20.json",
94    event_derives(serde::Deserialize, serde::Serialize)
95);
96
97impl EthBridgeEvent {
98    pub fn try_into_bridge_action(
99        self,
100        eth_tx_hash: ethers::types::H256,
101        eth_event_index: u16,
102    ) -> BridgeResult<Option<BridgeAction>> {
103        Ok(match self {
104            EthBridgeEvent::EthSuiBridgeEvents(event) => {
105                match event {
106                    EthSuiBridgeEvents::TokensDepositedFilter(event) => {
107                        let bridge_event = match EthToSuiTokenBridgeV1::try_from(&event) {
108                            Ok(bridge_event) => {
109                                if bridge_event.sui_adjusted_amount == 0 {
110                                    return Err(BridgeError::ZeroValueBridgeTransfer(format!(
111                                        "Manual intervention is required: {}",
112                                        eth_tx_hash
113                                    )));
114                                }
115                                bridge_event
116                            }
117                            Err(e) => {
122                                return Err(BridgeError::Generic(format!(
123                                    "Manual intervention is required. Failed to convert TokensDepositedFilter log to EthToSuiTokenBridgeV1. This indicates incorrect parameters or a bug in the code: {:?}. Err: {:?}",
124                                    event, e
125                                )));
126                            }
127                        };
128
129                        Some(BridgeAction::EthToSuiBridgeAction(EthToSuiBridgeAction {
130                            eth_tx_hash,
131                            eth_event_index,
132                            eth_bridge_event: bridge_event,
133                        }))
134                    }
135                    EthSuiBridgeEvents::TokensClaimedFilter(_event) => None,
136                    EthSuiBridgeEvents::PausedFilter(_event) => None,
137                    EthSuiBridgeEvents::UnpausedFilter(_event) => None,
138                    EthSuiBridgeEvents::UpgradedFilter(_event) => None,
139                    EthSuiBridgeEvents::InitializedFilter(_event) => None,
140                    EthSuiBridgeEvents::ContractUpgradedFilter(_event) => None,
141                    EthSuiBridgeEvents::EmergencyOperationFilter(_event) => None,
142                }
143            }
144            EthBridgeEvent::EthBridgeCommitteeEvents(event) => match event {
145                EthBridgeCommitteeEvents::BlocklistUpdatedFilter(_event) => None,
146                EthBridgeCommitteeEvents::InitializedFilter(_event) => None,
147                EthBridgeCommitteeEvents::UpgradedFilter(_event) => None,
148                EthBridgeCommitteeEvents::BlocklistUpdatedV2Filter(_event) => None,
149                EthBridgeCommitteeEvents::ContractUpgradedFilter(_event) => None,
150            },
151            EthBridgeEvent::EthBridgeLimiterEvents(event) => match event {
152                EthBridgeLimiterEvents::LimitUpdatedFilter(_event) => None,
153                EthBridgeLimiterEvents::InitializedFilter(_event) => None,
154                EthBridgeLimiterEvents::UpgradedFilter(_event) => None,
155                EthBridgeLimiterEvents::HourlyTransferAmountUpdatedFilter(_event) => None,
156                EthBridgeLimiterEvents::OwnershipTransferredFilter(_event) => None,
157                EthBridgeLimiterEvents::ContractUpgradedFilter(_event) => None,
158                EthBridgeLimiterEvents::LimitUpdatedV2Filter(_event) => None,
159            },
160            EthBridgeEvent::EthBridgeConfigEvents(event) => match event {
161                EthBridgeConfigEvents::InitializedFilter(_event) => None,
162                EthBridgeConfigEvents::UpgradedFilter(_event) => None,
163                EthBridgeConfigEvents::TokenAddedFilter(_event) => None,
164                EthBridgeConfigEvents::TokenPriceUpdatedFilter(_event) => None,
165                EthBridgeConfigEvents::ContractUpgradedFilter(_event) => None,
166                EthBridgeConfigEvents::TokenPriceUpdatedV2Filter(_event) => None,
167                EthBridgeConfigEvents::TokensAddedV2Filter(_event) => None,
168            },
169            EthBridgeEvent::EthCommitteeUpgradeableContractEvents(event) => match event {
170                EthCommitteeUpgradeableContractEvents::InitializedFilter(_event) => None,
171                EthCommitteeUpgradeableContractEvents::UpgradedFilter(_event) => None,
172            },
173        })
174    }
175}
176
177#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
180pub struct EthToSuiTokenBridgeV1 {
181    pub nonce: u64,
182    pub sui_chain_id: BridgeChainId,
183    pub eth_chain_id: BridgeChainId,
184    pub sui_address: SuiAddress,
185    pub eth_address: EthAddress,
186    pub token_id: u8,
187    pub sui_adjusted_amount: u64,
188}
189
190impl TryFrom<&TokensDepositedFilter> for EthToSuiTokenBridgeV1 {
191    type Error = BridgeError;
192    fn try_from(event: &TokensDepositedFilter) -> BridgeResult<Self> {
193        Ok(Self {
194            nonce: event.nonce,
195            sui_chain_id: BridgeChainId::try_from(event.destination_chain_id)?,
196            eth_chain_id: BridgeChainId::try_from(event.source_chain_id)?,
197            sui_address: SuiAddress::from_bytes(event.recipient_address.as_ref())?,
198            eth_address: event.sender_address,
199            token_id: event.token_id,
200            sui_adjusted_amount: event.sui_adjusted_amount,
201        })
202    }
203}
204
205impl TryFrom<SuiToEthBridgeAction> for eth_sui_bridge::Message {
210    type Error = BridgeError;
211
212    fn try_from(action: SuiToEthBridgeAction) -> BridgeResult<Self> {
213        Ok(eth_sui_bridge::Message {
214            message_type: BridgeActionType::TokenTransfer as u8,
215            version: TOKEN_TRANSFER_MESSAGE_VERSION,
216            nonce: action.sui_bridge_event.nonce,
217            chain_id: action.sui_bridge_event.sui_chain_id as u8,
218            payload: action
219                .as_payload_bytes()
220                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
221                .into(),
222        })
223    }
224}
225
226impl From<ParsedTokenTransferMessage> for eth_sui_bridge::Message {
227    fn from(parsed_message: ParsedTokenTransferMessage) -> Self {
228        eth_sui_bridge::Message {
229            message_type: BridgeActionType::TokenTransfer as u8,
230            version: parsed_message.message_version,
231            nonce: parsed_message.seq_num,
232            chain_id: parsed_message.source_chain as u8,
233            payload: parsed_message.payload.into(),
234        }
235    }
236}
237
238impl TryFrom<EmergencyAction> for eth_sui_bridge::Message {
239    type Error = BridgeError;
240
241    fn try_from(action: EmergencyAction) -> BridgeResult<Self> {
242        Ok(eth_sui_bridge::Message {
243            message_type: BridgeActionType::EmergencyButton as u8,
244            version: EMERGENCY_BUTTON_MESSAGE_VERSION,
245            nonce: action.nonce,
246            chain_id: action.chain_id as u8,
247            payload: action
248                .as_payload_bytes()
249                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
250                .into(),
251        })
252    }
253}
254
255impl TryFrom<BlocklistCommitteeAction> for eth_bridge_committee::Message {
256    type Error = BridgeError;
257
258    fn try_from(action: BlocklistCommitteeAction) -> BridgeResult<Self> {
259        Ok(eth_bridge_committee::Message {
260            message_type: BridgeActionType::UpdateCommitteeBlocklist as u8,
261            version: COMMITTEE_BLOCKLIST_MESSAGE_VERSION,
262            nonce: action.nonce,
263            chain_id: action.chain_id as u8,
264            payload: action
265                .as_payload_bytes()
266                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
267                .into(),
268        })
269    }
270}
271
272impl TryFrom<LimitUpdateAction> for eth_bridge_limiter::Message {
273    type Error = BridgeError;
274
275    fn try_from(action: LimitUpdateAction) -> BridgeResult<Self> {
276        Ok(eth_bridge_limiter::Message {
277            message_type: BridgeActionType::LimitUpdate as u8,
278            version: LIMIT_UPDATE_MESSAGE_VERSION,
279            nonce: action.nonce,
280            chain_id: action.chain_id as u8,
281            payload: action
282                .as_payload_bytes()
283                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
284                .into(),
285        })
286    }
287}
288
289impl TryFrom<AssetPriceUpdateAction> for eth_bridge_config::Message {
290    type Error = BridgeError;
291
292    fn try_from(action: AssetPriceUpdateAction) -> BridgeResult<Self> {
293        Ok(eth_bridge_config::Message {
294            message_type: BridgeActionType::AssetPriceUpdate as u8,
295            version: ASSET_PRICE_UPDATE_MESSAGE_VERSION,
296            nonce: action.nonce,
297            chain_id: action.chain_id as u8,
298            payload: action
299                .as_payload_bytes()
300                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
301                .into(),
302        })
303    }
304}
305
306impl TryFrom<AddTokensOnEvmAction> for eth_bridge_config::Message {
307    type Error = BridgeError;
308
309    fn try_from(action: AddTokensOnEvmAction) -> BridgeResult<Self> {
310        Ok(eth_bridge_config::Message {
311            message_type: BridgeActionType::AddTokensOnEvm as u8,
312            version: ADD_TOKENS_ON_EVM_MESSAGE_VERSION,
313            nonce: action.nonce,
314            chain_id: action.chain_id as u8,
315            payload: action
316                .as_payload_bytes()
317                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
318                .into(),
319        })
320    }
321}
322
323impl TryFrom<EvmContractUpgradeAction> for eth_committee_upgradeable_contract::Message {
324    type Error = BridgeError;
325
326    fn try_from(action: EvmContractUpgradeAction) -> BridgeResult<Self> {
327        Ok(eth_committee_upgradeable_contract::Message {
328            message_type: BridgeActionType::EvmContractUpgrade as u8,
329            version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
330            nonce: action.nonce,
331            chain_id: action.chain_id as u8,
332            payload: action
333                .as_payload_bytes()
334                .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
335                .into(),
336        })
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use crate::{
344        crypto::BridgeAuthorityPublicKeyBytes,
345        types::{BlocklistType, EmergencyActionType},
346    };
347    use ethers::types::TxHash;
348    use fastcrypto::encoding::{Encoding, Hex};
349    use hex_literal::hex;
350    use std::str::FromStr;
351    use sui_types::{bridge::TOKEN_ID_ETH, crypto::ToFromBytes};
352
353    #[test]
354    fn test_eth_message_conversion_emergency_action_regression() -> anyhow::Result<()> {
355        telemetry_subscribers::init_for_testing();
356
357        let action = EmergencyAction {
358            nonce: 2,
359            chain_id: BridgeChainId::EthSepolia,
360            action_type: EmergencyActionType::Pause,
361        };
362        let message: eth_sui_bridge::Message = action.try_into().unwrap();
363        assert_eq!(
364            message,
365            eth_sui_bridge::Message {
366                message_type: BridgeActionType::EmergencyButton as u8,
367                version: EMERGENCY_BUTTON_MESSAGE_VERSION,
368                nonce: 2,
369                chain_id: BridgeChainId::EthSepolia as u8,
370                payload: vec![0].into(),
371            }
372        );
373        Ok(())
374    }
375
376    #[test]
377    fn test_eth_message_conversion_update_blocklist_action_regression() -> anyhow::Result<()> {
378        telemetry_subscribers::init_for_testing();
379        let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
380            &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
381                .unwrap(),
382        )
383        .unwrap();
384        let action = BlocklistCommitteeAction {
385            nonce: 0,
386            chain_id: BridgeChainId::EthSepolia,
387            blocklist_type: BlocklistType::Blocklist,
388            members_to_update: vec![pub_key_bytes],
389        };
390        let message: eth_bridge_committee::Message = action.try_into().unwrap();
391        assert_eq!(
392            message,
393            eth_bridge_committee::Message {
394                message_type: BridgeActionType::UpdateCommitteeBlocklist as u8,
395                version: COMMITTEE_BLOCKLIST_MESSAGE_VERSION,
396                nonce: 0,
397                chain_id: BridgeChainId::EthSepolia as u8,
398                payload: Hex::decode("000168b43fd906c0b8f024a18c56e06744f7c6157c65")
399                    .unwrap()
400                    .into(),
401            }
402        );
403        Ok(())
404    }
405
406    #[test]
407    fn test_eth_message_conversion_update_limit_action_regression() -> anyhow::Result<()> {
408        telemetry_subscribers::init_for_testing();
409        let action = LimitUpdateAction {
410            nonce: 2,
411            chain_id: BridgeChainId::EthSepolia,
412            sending_chain_id: BridgeChainId::SuiTestnet,
413            new_usd_limit: 4200000,
414        };
415        let message: eth_bridge_limiter::Message = action.try_into().unwrap();
416        assert_eq!(
417            message,
418            eth_bridge_limiter::Message {
419                message_type: BridgeActionType::LimitUpdate as u8,
420                version: LIMIT_UPDATE_MESSAGE_VERSION,
421                nonce: 2,
422                chain_id: BridgeChainId::EthSepolia as u8,
423                payload: Hex::decode("010000000000401640").unwrap().into(),
424            }
425        );
426        Ok(())
427    }
428
429    #[test]
430    fn test_eth_message_conversion_contract_upgrade_action_regression() -> anyhow::Result<()> {
431        telemetry_subscribers::init_for_testing();
432        let action = EvmContractUpgradeAction {
433            nonce: 2,
434            chain_id: BridgeChainId::EthSepolia,
435            proxy_address: EthAddress::repeat_byte(1),
436            new_impl_address: EthAddress::repeat_byte(2),
437            call_data: Vec::from("deadbeef"),
438        };
439        let message: eth_committee_upgradeable_contract::Message = action.try_into().unwrap();
440        assert_eq!(
441            message,
442            eth_committee_upgradeable_contract::Message {
443                message_type: BridgeActionType::EvmContractUpgrade as u8,
444                version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
445                nonce: 2,
446                chain_id: BridgeChainId::EthSepolia as u8,
447                payload: Hex::decode("0x00000000000000000000000001010101010101010101010101010101010101010000000000000000000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000").unwrap().into(),
448            }
449        );
450        Ok(())
451    }
452
453    #[test]
454    fn test_eth_message_conversion_update_price_action_regression() -> anyhow::Result<()> {
455        telemetry_subscribers::init_for_testing();
456        let action = AssetPriceUpdateAction {
457            nonce: 2,
458            chain_id: BridgeChainId::EthSepolia,
459            token_id: TOKEN_ID_ETH,
460            new_usd_price: 80000000,
461        };
462        let message: eth_bridge_config::Message = action.try_into().unwrap();
463        assert_eq!(
464            message,
465            eth_bridge_config::Message {
466                message_type: BridgeActionType::AssetPriceUpdate as u8,
467                version: ASSET_PRICE_UPDATE_MESSAGE_VERSION,
468                nonce: 2,
469                chain_id: BridgeChainId::EthSepolia as u8,
470                payload: Hex::decode("020000000004c4b400").unwrap().into(),
471            }
472        );
473        Ok(())
474    }
475
476    #[test]
477    fn test_eth_message_conversion_add_tokens_on_evm_action_regression() -> anyhow::Result<()> {
478        let action = AddTokensOnEvmAction {
479            nonce: 5,
480            chain_id: BridgeChainId::EthCustom,
481            native: true,
482            token_ids: vec![99, 100, 101],
483            token_addresses: vec![
484                EthAddress::repeat_byte(1),
485                EthAddress::repeat_byte(2),
486                EthAddress::repeat_byte(3),
487            ],
488            token_sui_decimals: vec![5, 6, 7],
489            token_prices: vec![1_000_000_000, 2_000_000_000, 3_000_000_000],
490        };
491        let message: eth_bridge_config::Message = action.try_into().unwrap();
492        assert_eq!(
493            message,
494            eth_bridge_config::Message {
495                message_type: BridgeActionType::AddTokensOnEvm as u8,
496                version: ADD_TOKENS_ON_EVM_MESSAGE_VERSION,
497                nonce: 5,
498                chain_id: BridgeChainId::EthCustom as u8,
499                payload: Hex::decode("0103636465030101010101010101010101010101010101010101020202020202020202020202020202020202020203030303030303030303030303030303030303030305060703000000003b9aca00000000007735940000000000b2d05e00").unwrap().into(),
500            }
501        );
502        Ok(())
503    }
504
505    #[test]
506    fn test_token_deposit_eth_log_to_sui_bridge_event_regression() -> anyhow::Result<()> {
507        telemetry_subscribers::init_for_testing();
508        let tx_hash = TxHash::random();
509        let action = EthLog {
510            block_number: 33,
511            tx_hash,
512            log_index_in_tx: 1,
513            log: Log {
514                address: EthAddress::repeat_byte(1),
515                topics: vec![
516                    hex!("a0f1d54820817ede8517e70a3d0a9197c015471c5360d2119b759f0359858ce6").into(),
517                    hex!("000000000000000000000000000000000000000000000000000000000000000c").into(),
518                    hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
519                    hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
520                ],
521                data: ethers::types::Bytes::from(
522                    Hex::decode("0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000fa56ea0000000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000203b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77").unwrap(),
523                ),
524                block_hash: None,
525                block_number: None,
526                transaction_hash: Some(tx_hash),
527                transaction_index: Some(ethers::types::U64::from(0)),
528                log_index: Some(ethers::types::U256::from(1)),
529                transaction_log_index: None,
530                log_type: None,
531                removed: Some(false),
532            }
533        };
534        let event = EthBridgeEvent::try_from_eth_log(&action).unwrap();
535        assert_eq!(
536            event,
537            EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
538                TokensDepositedFilter {
539                    source_chain_id: 12,
540                    nonce: 0,
541                    destination_chain_id: 2,
542                    token_id: 2,
543                    sui_adjusted_amount: 4200000000,
544                    sender_address: EthAddress::from_str(
545                        "0x14dc79964da2c08b23698b3d3cc7ca32193d9955"
546                    )
547                    .unwrap(),
548                    recipient_address: ethers::types::Bytes::from(
549                        Hex::decode(
550                            "0x3b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77"
551                        )
552                        .unwrap(),
553                    ),
554                }
555            ))
556        );
557        Ok(())
558    }
559
560    #[test]
561    fn test_0_sui_amount_conversion_for_eth_event() {
562        let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
563            TokensDepositedFilter {
564                source_chain_id: BridgeChainId::EthSepolia as u8,
565                nonce: 0,
566                destination_chain_id: BridgeChainId::SuiTestnet as u8,
567                token_id: 2,
568                sui_adjusted_amount: 1,
569                sender_address: EthAddress::random(),
570                recipient_address: ethers::types::Bytes::from(
571                    SuiAddress::random_for_testing_only().to_vec(),
572                ),
573            },
574        ));
575        assert!(
576            e.try_into_bridge_action(TxHash::random(), 0)
577                .unwrap()
578                .is_some()
579        );
580
581        let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
582            TokensDepositedFilter {
583                source_chain_id: BridgeChainId::EthSepolia as u8,
584                nonce: 0,
585                destination_chain_id: BridgeChainId::SuiTestnet as u8,
586                token_id: 2,
587                sui_adjusted_amount: 0, sender_address: EthAddress::random(),
589                recipient_address: ethers::types::Bytes::from(
590                    SuiAddress::random_for_testing_only().to_vec(),
591                ),
592            },
593        ));
594        match e.try_into_bridge_action(TxHash::random(), 0).unwrap_err() {
595            BridgeError::ZeroValueBridgeTransfer(_) => {}
596            e => panic!("Unexpected error: {:?}", e),
597        }
598    }
599}