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, SuiToEthTokenTransfer,
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 TryFrom<SuiToEthTokenTransfer> for eth_sui_bridge::Message {
227 type Error = BridgeError;
228
229 fn try_from(action: SuiToEthTokenTransfer) -> BridgeResult<Self> {
230 Ok(eth_sui_bridge::Message {
231 message_type: BridgeActionType::TokenTransfer as u8,
232 version: TOKEN_TRANSFER_MESSAGE_VERSION,
233 nonce: action.nonce,
234 chain_id: action.sui_chain_id as u8,
235 payload: action
236 .as_payload_bytes()
237 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
238 .into(),
239 })
240 }
241}
242
243impl From<ParsedTokenTransferMessage> for eth_sui_bridge::Message {
244 fn from(parsed_message: ParsedTokenTransferMessage) -> Self {
245 eth_sui_bridge::Message {
246 message_type: BridgeActionType::TokenTransfer as u8,
247 version: parsed_message.message_version,
248 nonce: parsed_message.seq_num,
249 chain_id: parsed_message.source_chain as u8,
250 payload: parsed_message.payload.into(),
251 }
252 }
253}
254
255impl TryFrom<EmergencyAction> for eth_sui_bridge::Message {
256 type Error = BridgeError;
257
258 fn try_from(action: EmergencyAction) -> BridgeResult<Self> {
259 Ok(eth_sui_bridge::Message {
260 message_type: BridgeActionType::EmergencyButton as u8,
261 version: EMERGENCY_BUTTON_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<BlocklistCommitteeAction> for eth_bridge_committee::Message {
273 type Error = BridgeError;
274
275 fn try_from(action: BlocklistCommitteeAction) -> BridgeResult<Self> {
276 Ok(eth_bridge_committee::Message {
277 message_type: BridgeActionType::UpdateCommitteeBlocklist as u8,
278 version: COMMITTEE_BLOCKLIST_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<LimitUpdateAction> for eth_bridge_limiter::Message {
290 type Error = BridgeError;
291
292 fn try_from(action: LimitUpdateAction) -> BridgeResult<Self> {
293 Ok(eth_bridge_limiter::Message {
294 message_type: BridgeActionType::LimitUpdate as u8,
295 version: LIMIT_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<AssetPriceUpdateAction> for eth_bridge_config::Message {
307 type Error = BridgeError;
308
309 fn try_from(action: AssetPriceUpdateAction) -> BridgeResult<Self> {
310 Ok(eth_bridge_config::Message {
311 message_type: BridgeActionType::AssetPriceUpdate as u8,
312 version: ASSET_PRICE_UPDATE_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<AddTokensOnEvmAction> for eth_bridge_config::Message {
324 type Error = BridgeError;
325
326 fn try_from(action: AddTokensOnEvmAction) -> BridgeResult<Self> {
327 Ok(eth_bridge_config::Message {
328 message_type: BridgeActionType::AddTokensOnEvm as u8,
329 version: ADD_TOKENS_ON_EVM_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
340impl TryFrom<EvmContractUpgradeAction> for eth_committee_upgradeable_contract::Message {
341 type Error = BridgeError;
342
343 fn try_from(action: EvmContractUpgradeAction) -> BridgeResult<Self> {
344 Ok(eth_committee_upgradeable_contract::Message {
345 message_type: BridgeActionType::EvmContractUpgrade as u8,
346 version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
347 nonce: action.nonce,
348 chain_id: action.chain_id as u8,
349 payload: action
350 .as_payload_bytes()
351 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
352 .into(),
353 })
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use crate::{
361 crypto::BridgeAuthorityPublicKeyBytes,
362 types::{BlocklistType, EmergencyActionType},
363 };
364 use ethers::types::TxHash;
365 use fastcrypto::encoding::{Encoding, Hex};
366 use hex_literal::hex;
367 use std::str::FromStr;
368 use sui_types::{bridge::TOKEN_ID_ETH, crypto::ToFromBytes};
369
370 #[test]
371 fn test_eth_message_conversion_emergency_action_regression() -> anyhow::Result<()> {
372 telemetry_subscribers::init_for_testing();
373
374 let action = EmergencyAction {
375 nonce: 2,
376 chain_id: BridgeChainId::EthSepolia,
377 action_type: EmergencyActionType::Pause,
378 };
379 let message: eth_sui_bridge::Message = action.try_into().unwrap();
380 assert_eq!(
381 message,
382 eth_sui_bridge::Message {
383 message_type: BridgeActionType::EmergencyButton as u8,
384 version: EMERGENCY_BUTTON_MESSAGE_VERSION,
385 nonce: 2,
386 chain_id: BridgeChainId::EthSepolia as u8,
387 payload: vec![0].into(),
388 }
389 );
390 Ok(())
391 }
392
393 #[test]
394 fn test_eth_message_conversion_update_blocklist_action_regression() -> anyhow::Result<()> {
395 telemetry_subscribers::init_for_testing();
396 let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
397 &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
398 .unwrap(),
399 )
400 .unwrap();
401 let action = BlocklistCommitteeAction {
402 nonce: 0,
403 chain_id: BridgeChainId::EthSepolia,
404 blocklist_type: BlocklistType::Blocklist,
405 members_to_update: vec![pub_key_bytes],
406 };
407 let message: eth_bridge_committee::Message = action.try_into().unwrap();
408 assert_eq!(
409 message,
410 eth_bridge_committee::Message {
411 message_type: BridgeActionType::UpdateCommitteeBlocklist as u8,
412 version: COMMITTEE_BLOCKLIST_MESSAGE_VERSION,
413 nonce: 0,
414 chain_id: BridgeChainId::EthSepolia as u8,
415 payload: Hex::decode("000168b43fd906c0b8f024a18c56e06744f7c6157c65")
416 .unwrap()
417 .into(),
418 }
419 );
420 Ok(())
421 }
422
423 #[test]
424 fn test_eth_message_conversion_update_limit_action_regression() -> anyhow::Result<()> {
425 telemetry_subscribers::init_for_testing();
426 let action = LimitUpdateAction {
427 nonce: 2,
428 chain_id: BridgeChainId::EthSepolia,
429 sending_chain_id: BridgeChainId::SuiTestnet,
430 new_usd_limit: 4200000,
431 };
432 let message: eth_bridge_limiter::Message = action.try_into().unwrap();
433 assert_eq!(
434 message,
435 eth_bridge_limiter::Message {
436 message_type: BridgeActionType::LimitUpdate as u8,
437 version: LIMIT_UPDATE_MESSAGE_VERSION,
438 nonce: 2,
439 chain_id: BridgeChainId::EthSepolia as u8,
440 payload: Hex::decode("010000000000401640").unwrap().into(),
441 }
442 );
443 Ok(())
444 }
445
446 #[test]
447 fn test_eth_message_conversion_contract_upgrade_action_regression() -> anyhow::Result<()> {
448 telemetry_subscribers::init_for_testing();
449 let action = EvmContractUpgradeAction {
450 nonce: 2,
451 chain_id: BridgeChainId::EthSepolia,
452 proxy_address: EthAddress::repeat_byte(1),
453 new_impl_address: EthAddress::repeat_byte(2),
454 call_data: Vec::from("deadbeef"),
455 };
456 let message: eth_committee_upgradeable_contract::Message = action.try_into().unwrap();
457 assert_eq!(
458 message,
459 eth_committee_upgradeable_contract::Message {
460 message_type: BridgeActionType::EvmContractUpgrade as u8,
461 version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
462 nonce: 2,
463 chain_id: BridgeChainId::EthSepolia as u8,
464 payload: Hex::decode("0x00000000000000000000000001010101010101010101010101010101010101010000000000000000000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000").unwrap().into(),
465 }
466 );
467 Ok(())
468 }
469
470 #[test]
471 fn test_eth_message_conversion_update_price_action_regression() -> anyhow::Result<()> {
472 telemetry_subscribers::init_for_testing();
473 let action = AssetPriceUpdateAction {
474 nonce: 2,
475 chain_id: BridgeChainId::EthSepolia,
476 token_id: TOKEN_ID_ETH,
477 new_usd_price: 80000000,
478 };
479 let message: eth_bridge_config::Message = action.try_into().unwrap();
480 assert_eq!(
481 message,
482 eth_bridge_config::Message {
483 message_type: BridgeActionType::AssetPriceUpdate as u8,
484 version: ASSET_PRICE_UPDATE_MESSAGE_VERSION,
485 nonce: 2,
486 chain_id: BridgeChainId::EthSepolia as u8,
487 payload: Hex::decode("020000000004c4b400").unwrap().into(),
488 }
489 );
490 Ok(())
491 }
492
493 #[test]
494 fn test_eth_message_conversion_add_tokens_on_evm_action_regression() -> anyhow::Result<()> {
495 let action = AddTokensOnEvmAction {
496 nonce: 5,
497 chain_id: BridgeChainId::EthCustom,
498 native: true,
499 token_ids: vec![99, 100, 101],
500 token_addresses: vec![
501 EthAddress::repeat_byte(1),
502 EthAddress::repeat_byte(2),
503 EthAddress::repeat_byte(3),
504 ],
505 token_sui_decimals: vec![5, 6, 7],
506 token_prices: vec![1_000_000_000, 2_000_000_000, 3_000_000_000],
507 };
508 let message: eth_bridge_config::Message = action.try_into().unwrap();
509 assert_eq!(
510 message,
511 eth_bridge_config::Message {
512 message_type: BridgeActionType::AddTokensOnEvm as u8,
513 version: ADD_TOKENS_ON_EVM_MESSAGE_VERSION,
514 nonce: 5,
515 chain_id: BridgeChainId::EthCustom as u8,
516 payload: Hex::decode("0103636465030101010101010101010101010101010101010101020202020202020202020202020202020202020203030303030303030303030303030303030303030305060703000000003b9aca00000000007735940000000000b2d05e00").unwrap().into(),
517 }
518 );
519 Ok(())
520 }
521
522 #[test]
523 fn test_token_deposit_eth_log_to_sui_bridge_event_regression() -> anyhow::Result<()> {
524 telemetry_subscribers::init_for_testing();
525 let tx_hash = TxHash::random();
526 let action = EthLog {
527 block_number: 33,
528 tx_hash,
529 log_index_in_tx: 1,
530 log: Log {
531 address: EthAddress::repeat_byte(1),
532 topics: vec![
533 hex!("a0f1d54820817ede8517e70a3d0a9197c015471c5360d2119b759f0359858ce6").into(),
534 hex!("000000000000000000000000000000000000000000000000000000000000000c").into(),
535 hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
536 hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
537 ],
538 data: ethers::types::Bytes::from(
539 Hex::decode("0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000fa56ea0000000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000203b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77").unwrap(),
540 ),
541 block_hash: None,
542 block_number: None,
543 transaction_hash: Some(tx_hash),
544 transaction_index: Some(ethers::types::U64::from(0)),
545 log_index: Some(ethers::types::U256::from(1)),
546 transaction_log_index: None,
547 log_type: None,
548 removed: Some(false),
549 }
550 };
551 let event = EthBridgeEvent::try_from_eth_log(&action).unwrap();
552 assert_eq!(
553 event,
554 EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
555 TokensDepositedFilter {
556 source_chain_id: 12,
557 nonce: 0,
558 destination_chain_id: 2,
559 token_id: 2,
560 sui_adjusted_amount: 4200000000,
561 sender_address: EthAddress::from_str(
562 "0x14dc79964da2c08b23698b3d3cc7ca32193d9955"
563 )
564 .unwrap(),
565 recipient_address: ethers::types::Bytes::from(
566 Hex::decode(
567 "0x3b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77"
568 )
569 .unwrap(),
570 ),
571 }
572 ))
573 );
574 Ok(())
575 }
576
577 #[test]
578 fn test_0_sui_amount_conversion_for_eth_event() {
579 let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
580 TokensDepositedFilter {
581 source_chain_id: BridgeChainId::EthSepolia as u8,
582 nonce: 0,
583 destination_chain_id: BridgeChainId::SuiTestnet as u8,
584 token_id: 2,
585 sui_adjusted_amount: 1,
586 sender_address: EthAddress::random(),
587 recipient_address: ethers::types::Bytes::from(
588 SuiAddress::random_for_testing_only().to_vec(),
589 ),
590 },
591 ));
592 assert!(
593 e.try_into_bridge_action(TxHash::random(), 0)
594 .unwrap()
595 .is_some()
596 );
597
598 let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDepositedFilter(
599 TokensDepositedFilter {
600 source_chain_id: BridgeChainId::EthSepolia as u8,
601 nonce: 0,
602 destination_chain_id: BridgeChainId::SuiTestnet as u8,
603 token_id: 2,
604 sui_adjusted_amount: 0, sender_address: EthAddress::random(),
606 recipient_address: ethers::types::Bytes::from(
607 SuiAddress::random_for_testing_only().to_vec(),
608 ),
609 },
610 ));
611 match e.try_into_bridge_action(TxHash::random(), 0).unwrap_err() {
612 BridgeError::ZeroValueBridgeTransfer(_) => {}
613 e => panic!("Unexpected error: {:?}", e),
614 }
615 }
616}