1use crate::encoding::{
5 ADD_TOKENS_ON_EVM_MESSAGE_VERSION, ASSET_PRICE_UPDATE_MESSAGE_VERSION, BridgeMessageEncoding,
6 COMMITTEE_BLOCKLIST_MESSAGE_VERSION, EMERGENCY_BUTTON_MESSAGE_VERSION,
7 EVM_CONTRACT_UPGRADE_MESSAGE_VERSION, LIMIT_UPDATE_MESSAGE_VERSION,
8 TOKEN_TRANSFER_MESSAGE_VERSION_V1, TOKEN_TRANSFER_MESSAGE_VERSION_V2,
9};
10use crate::error::{BridgeError, BridgeResult};
11use crate::types::{
12 AddTokensOnEvmAction, AssetPriceUpdateAction, BlocklistCommitteeAction, BridgeAction,
13 BridgeActionType, EmergencyAction, EthLog, EthToSuiBridgeAction, EthToSuiTokenTransferV2,
14 EvmContractUpgradeAction, LimitUpdateAction, ParsedTokenTransferMessage, SuiToEthBridgeAction,
15 SuiToEthTokenTransfer, SuiToEthTokenTransferV2,
16};
17use alloy::primitives::{Address as EthAddress, TxHash};
18use alloy::rpc::types::eth::Log;
19use alloy::sol;
20use alloy::sol_types::SolEventInterface;
21use serde::{Deserialize, Serialize};
22use sui_types::base_types::SuiAddress;
23use sui_types::bridge::BridgeChainId;
24
25macro_rules! gen_eth_events {
26 ($($contract:ident, $module:ident, $contract_event:ident, $abi_path:literal),* $(,)?) => {
28 $(
29 pub mod $module {
32 alloy::sol!(
33 #[sol(rpc, all_derives, extra_derives(serde::Serialize, serde::Deserialize))]
34 $contract,
35 $abi_path,
36 );
37 }
38
39 pub use $module::$contract;
41 #[allow(ambiguous_glob_reexports)]
42 pub use $module::$contract::*;
43 )*
44
45 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46 pub enum EthBridgeEvent {
47 $(
48 $contract_event($contract_event),
49 )*
50 }
51
52 impl EthBridgeEvent {
53 pub fn try_from_eth_log(log: &EthLog) -> Option<EthBridgeEvent> {
54 Self::try_from_log(&log.log)
55 }
56
57 pub fn try_from_log(log: &Log) -> Option<EthBridgeEvent> {
58 $(
59 if let Ok(decoded) = $contract_event::decode_raw_log(log.topics(), &log.data().data) {
60 return Some(EthBridgeEvent::$contract_event(decoded));
61 }
62 )*
63
64 None
65 }
66 }
67 };
68
69 ($($contract:ident, $module:ident, $abi_path:literal),* $(,)?) => {
71 $(
72 pub mod $module {
73 alloy::sol!(
74 #[sol(rpc, extra_derives(serde::Serialize, serde::Deserialize))]
75 $contract,
76 $abi_path,
77 );
78 }
79
80 pub use $module::$contract;
81 )*
82 };
83}
84
85#[rustfmt::skip]
86gen_eth_events!(
87 EthSuiBridge, eth_sui_bridge, EthSuiBridgeEvents, "abi/sui_bridge.json",
89 EthBridgeCommittee, eth_bridge_committee, EthBridgeCommitteeEvents, "abi/bridge_committee.json",
90 EthBridgeLimiter, eth_bridge_limiter, EthBridgeLimiterEvents, "abi/bridge_limiter.json",
91 EthBridgeConfig, eth_bridge_config, EthBridgeConfigEvents, "abi/bridge_config.json",
92 EthCommitteeUpgradeableContract, eth_committee_upgradeable_contract, EthCommitteeUpgradeableContractEvents, "abi/bridge_committee_upgradeable.json",
93);
94
95gen_eth_events!(
96 EthBridgeVault,
98 eth_bridge_vault,
99 "abi/bridge_vault.json"
100);
101
102sol!(
103 #[sol(rpc, extra_derives(serde::Serialize, serde::Deserialize))]
104 EthERC20,
105 "abi/erc20.json",
106);
107
108impl EthBridgeEvent {
109 pub fn try_into_bridge_action(
110 self,
111 eth_tx_hash: TxHash,
112 eth_event_index: u16,
113 ) -> BridgeResult<Option<BridgeAction>> {
114 Ok(match self {
115 EthBridgeEvent::EthSuiBridgeEvents(event) => {
116 match event {
117 EthSuiBridgeEvents::TokensDeposited(event) => {
118 let bridge_event = match EthToSuiTokenBridgeV1::try_from(&event) {
119 Ok(bridge_event) => {
120 if bridge_event.sui_adjusted_amount == 0 {
121 return Err(BridgeError::ZeroValueBridgeTransfer(format!(
122 "Manual intervention is required: {}",
123 eth_tx_hash
124 )));
125 }
126 bridge_event
127 }
128 Err(e) => {
133 return Err(BridgeError::Generic(format!(
134 "Manual intervention is required. Failed to convert TokensDepositedFilter log to EthToSuiTokenBridgeV1. This indicates incorrect parameters or a bug in the code: {:?}. Err: {:?}",
135 event, e
136 )));
137 }
138 };
139
140 Some(BridgeAction::EthToSuiBridgeAction(EthToSuiBridgeAction {
141 eth_tx_hash,
142 eth_event_index,
143 eth_bridge_event: bridge_event,
144 }))
145 }
146 EthSuiBridgeEvents::TokensDepositedV2(event) => {
147 let eth_bridge_event = EthToSuiTokenBridgeV2::try_from(&event)?;
148 Some(BridgeAction::EthToSuiTokenTransferV2(
149 EthToSuiTokenTransferV2 {
150 eth_tx_hash,
151 eth_event_index,
152 eth_bridge_event,
153 },
154 ))
155 }
156 EthSuiBridgeEvents::TokensClaimed(_event) => None,
157 EthSuiBridgeEvents::Paused(_event) => None,
158 EthSuiBridgeEvents::Unpaused(_event) => None,
159 EthSuiBridgeEvents::Upgraded(_event) => None,
160 EthSuiBridgeEvents::Initialized(_event) => None,
161 EthSuiBridgeEvents::ContractUpgraded(_event) => None,
162 EthSuiBridgeEvents::EmergencyOperation(_event) => None,
163 }
164 }
165 EthBridgeEvent::EthBridgeCommitteeEvents(event) => match event {
166 EthBridgeCommitteeEvents::BlocklistUpdated(_event) => None,
167 EthBridgeCommitteeEvents::Initialized(_event) => None,
168 EthBridgeCommitteeEvents::Upgraded(_event) => None,
169 EthBridgeCommitteeEvents::BlocklistUpdatedV2(_event) => None,
170 EthBridgeCommitteeEvents::ContractUpgraded(_event) => None,
171 },
172 EthBridgeEvent::EthBridgeLimiterEvents(event) => match event {
173 EthBridgeLimiterEvents::LimitUpdated(_event) => None,
174 EthBridgeLimiterEvents::Initialized(_event) => None,
175 EthBridgeLimiterEvents::Upgraded(_event) => None,
176 EthBridgeLimiterEvents::HourlyTransferAmountUpdated(_event) => None,
177 EthBridgeLimiterEvents::OwnershipTransferred(_event) => None,
178 EthBridgeLimiterEvents::ContractUpgraded(_event) => None,
179 EthBridgeLimiterEvents::LimitUpdatedV2(_event) => None,
180 },
181 EthBridgeEvent::EthBridgeConfigEvents(event) => match event {
182 EthBridgeConfigEvents::Initialized(_event) => None,
183 EthBridgeConfigEvents::Upgraded(_event) => None,
184 EthBridgeConfigEvents::TokenAdded(_event) => None,
185 EthBridgeConfigEvents::TokenPriceUpdated(_event) => None,
186 EthBridgeConfigEvents::ContractUpgraded(_event) => None,
187 EthBridgeConfigEvents::TokenPriceUpdatedV2(_event) => None,
188 EthBridgeConfigEvents::TokensAddedV2(_event) => None,
189 },
190 EthBridgeEvent::EthCommitteeUpgradeableContractEvents(event) => match event {
191 EthCommitteeUpgradeableContractEvents::Initialized(_event) => None,
192 EthCommitteeUpgradeableContractEvents::Upgraded(_event) => None,
193 },
194 })
195 }
196}
197
198#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
201pub struct EthToSuiTokenBridgeV1 {
202 pub nonce: u64,
203 pub sui_chain_id: BridgeChainId,
204 pub eth_chain_id: BridgeChainId,
205 pub sui_address: SuiAddress,
206 pub eth_address: EthAddress,
207 pub token_id: u8,
208 pub sui_adjusted_amount: u64,
209}
210
211impl TryFrom<&TokensDeposited> for EthToSuiTokenBridgeV1 {
212 type Error = BridgeError;
213
214 fn try_from(event: &TokensDeposited) -> BridgeResult<Self> {
215 Ok(Self {
216 nonce: event.nonce,
217 sui_chain_id: BridgeChainId::try_from(event.destinationChainID)?,
218 eth_chain_id: BridgeChainId::try_from(event.sourceChainID)?,
219 sui_address: SuiAddress::from_bytes(event.recipientAddress.as_ref())?,
220 eth_address: event.senderAddress,
221 token_id: event.tokenID,
222 sui_adjusted_amount: event.suiAdjustedAmount,
223 })
224 }
225}
226
227#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
229pub struct EthToSuiTokenBridgeV2 {
230 pub nonce: u64,
231 pub sui_chain_id: BridgeChainId,
232 pub eth_chain_id: BridgeChainId,
233 pub sui_address: SuiAddress,
234 pub eth_address: EthAddress,
235 pub token_id: u8,
236 pub sui_adjusted_amount: u64,
237 pub timestamp_seconds: u64,
238}
239
240impl TryFrom<&TokensDepositedV2> for EthToSuiTokenBridgeV2 {
241 type Error = BridgeError;
242
243 fn try_from(event: &TokensDepositedV2) -> BridgeResult<Self> {
244 Ok(Self {
245 nonce: event.nonce,
246 sui_chain_id: BridgeChainId::try_from(event.destinationChainID)?,
247 eth_chain_id: BridgeChainId::try_from(event.sourceChainID)?,
248 sui_address: SuiAddress::from_bytes(event.recipientAddress.as_ref())?,
249 eth_address: event.senderAddress,
250 token_id: event.tokenID,
251 sui_adjusted_amount: event.suiAdjustedAmount,
252 timestamp_seconds: event.timestampSeconds.to::<u64>(),
253 })
254 }
255}
256
257impl From<EthToSuiTokenBridgeV2> for EthToSuiTokenBridgeV1 {
258 fn from(value: EthToSuiTokenBridgeV2) -> Self {
259 Self {
260 nonce: value.nonce,
261 sui_chain_id: value.sui_chain_id,
262 eth_chain_id: value.eth_chain_id,
263 sui_address: value.sui_address,
264 eth_address: value.eth_address,
265 token_id: value.token_id,
266 sui_adjusted_amount: value.sui_adjusted_amount,
267 }
268 }
269}
270
271impl TryFrom<SuiToEthBridgeAction> for eth_sui_bridge::BridgeUtils::Message {
276 type Error = BridgeError;
277
278 fn try_from(action: SuiToEthBridgeAction) -> BridgeResult<Self> {
279 Ok(eth_sui_bridge::BridgeUtils::Message {
280 messageType: BridgeActionType::TokenTransfer as u8,
281 version: TOKEN_TRANSFER_MESSAGE_VERSION_V1,
282 nonce: action.sui_bridge_event.nonce,
283 chainID: action.sui_bridge_event.sui_chain_id as u8,
284 payload: action
285 .as_payload_bytes()
286 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
287 .into(),
288 })
289 }
290}
291
292impl TryFrom<SuiToEthTokenTransfer> for eth_sui_bridge::BridgeUtils::Message {
293 type Error = BridgeError;
294
295 fn try_from(action: SuiToEthTokenTransfer) -> BridgeResult<Self> {
296 Ok(eth_sui_bridge::BridgeUtils::Message {
297 messageType: BridgeActionType::TokenTransfer as u8,
298 version: TOKEN_TRANSFER_MESSAGE_VERSION_V1,
299 nonce: action.nonce,
300 chainID: action.sui_chain_id as u8,
301 payload: action
302 .as_payload_bytes()
303 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
304 .into(),
305 })
306 }
307}
308
309impl TryFrom<SuiToEthTokenTransferV2> for eth_sui_bridge::BridgeUtils::Message {
310 type Error = BridgeError;
311
312 fn try_from(action: SuiToEthTokenTransferV2) -> BridgeResult<Self> {
313 Ok(eth_sui_bridge::BridgeUtils::Message {
314 messageType: BridgeActionType::TokenTransfer as u8,
315 version: TOKEN_TRANSFER_MESSAGE_VERSION_V2,
316 nonce: action.nonce,
317 chainID: action.sui_chain_id as u8,
318 payload: action
319 .as_payload_bytes()
320 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
321 .into(),
322 })
323 }
324}
325
326impl From<ParsedTokenTransferMessage> for eth_sui_bridge::BridgeUtils::Message {
327 fn from(parsed_message: ParsedTokenTransferMessage) -> Self {
328 eth_sui_bridge::BridgeUtils::Message {
329 messageType: BridgeActionType::TokenTransfer as u8,
330 version: parsed_message.message_version,
331 nonce: parsed_message.seq_num,
332 chainID: parsed_message.source_chain as u8,
333 payload: parsed_message.payload.into(),
334 }
335 }
336}
337
338impl TryFrom<EmergencyAction> for eth_sui_bridge::BridgeUtils::Message {
339 type Error = BridgeError;
340
341 fn try_from(action: EmergencyAction) -> BridgeResult<Self> {
342 Ok(eth_sui_bridge::BridgeUtils::Message {
343 messageType: BridgeActionType::EmergencyButton as u8,
344 version: EMERGENCY_BUTTON_MESSAGE_VERSION,
345 nonce: action.nonce,
346 chainID: action.chain_id as u8,
347 payload: action
348 .as_payload_bytes()
349 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
350 .into(),
351 })
352 }
353}
354
355impl TryFrom<BlocklistCommitteeAction> for eth_bridge_committee::BridgeUtils::Message {
356 type Error = BridgeError;
357
358 fn try_from(action: BlocklistCommitteeAction) -> BridgeResult<Self> {
359 Ok(eth_bridge_committee::BridgeUtils::Message {
360 messageType: BridgeActionType::UpdateCommitteeBlocklist as u8,
361 version: COMMITTEE_BLOCKLIST_MESSAGE_VERSION,
362 nonce: action.nonce,
363 chainID: action.chain_id as u8,
364 payload: action
365 .as_payload_bytes()
366 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
367 .into(),
368 })
369 }
370}
371
372impl TryFrom<LimitUpdateAction> for eth_bridge_limiter::BridgeUtils::Message {
373 type Error = BridgeError;
374
375 fn try_from(action: LimitUpdateAction) -> BridgeResult<Self> {
376 Ok(eth_bridge_limiter::BridgeUtils::Message {
377 messageType: BridgeActionType::LimitUpdate as u8,
378 version: LIMIT_UPDATE_MESSAGE_VERSION,
379 nonce: action.nonce,
380 chainID: action.chain_id as u8,
381 payload: action
382 .as_payload_bytes()
383 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
384 .into(),
385 })
386 }
387}
388
389impl TryFrom<AssetPriceUpdateAction> for eth_bridge_config::BridgeUtils::Message {
390 type Error = BridgeError;
391
392 fn try_from(action: AssetPriceUpdateAction) -> BridgeResult<Self> {
393 Ok(eth_bridge_config::BridgeUtils::Message {
394 messageType: BridgeActionType::AssetPriceUpdate as u8,
395 version: ASSET_PRICE_UPDATE_MESSAGE_VERSION,
396 nonce: action.nonce,
397 chainID: action.chain_id as u8,
398 payload: action
399 .as_payload_bytes()
400 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
401 .into(),
402 })
403 }
404}
405
406impl TryFrom<AddTokensOnEvmAction> for eth_bridge_config::BridgeUtils::Message {
407 type Error = BridgeError;
408
409 fn try_from(action: AddTokensOnEvmAction) -> BridgeResult<Self> {
410 Ok(eth_bridge_config::BridgeUtils::Message {
411 messageType: BridgeActionType::AddTokensOnEvm as u8,
412 version: ADD_TOKENS_ON_EVM_MESSAGE_VERSION,
413 nonce: action.nonce,
414 chainID: action.chain_id as u8,
415 payload: action
416 .as_payload_bytes()
417 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
418 .into(),
419 })
420 }
421}
422
423impl TryFrom<EvmContractUpgradeAction>
424 for eth_committee_upgradeable_contract::BridgeUtils::Message
425{
426 type Error = BridgeError;
427
428 fn try_from(action: EvmContractUpgradeAction) -> BridgeResult<Self> {
429 Ok(eth_committee_upgradeable_contract::BridgeUtils::Message {
430 messageType: BridgeActionType::EvmContractUpgrade as u8,
431 version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
432 nonce: action.nonce,
433 chainID: action.chain_id as u8,
434 payload: action
435 .as_payload_bytes()
436 .map_err(|e| BridgeError::Generic(format!("Failed to encode payload: {}", e)))?
437 .into(),
438 })
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use crate::{
446 crypto::BridgeAuthorityPublicKeyBytes,
447 types::{BlocklistType, EmergencyActionType},
448 };
449 use alloy::primitives::{B256, Bytes, LogData};
450 use fastcrypto::encoding::{Encoding, Hex};
451 use hex_literal::hex;
452 use std::str::FromStr;
453 use sui_types::{bridge::TOKEN_ID_ETH, crypto::ToFromBytes};
454
455 #[test]
456 fn test_eth_message_conversion_emergency_action_regression() -> anyhow::Result<()> {
457 telemetry_subscribers::init_for_testing();
458
459 let action = EmergencyAction {
460 nonce: 2,
461 chain_id: BridgeChainId::EthSepolia,
462 action_type: EmergencyActionType::Pause,
463 };
464 let message: eth_sui_bridge::BridgeUtils::Message = action.try_into().unwrap();
465 assert_eq!(
466 message,
467 eth_sui_bridge::BridgeUtils::Message {
468 messageType: BridgeActionType::EmergencyButton as u8,
469 version: EMERGENCY_BUTTON_MESSAGE_VERSION,
470 nonce: 2,
471 chainID: BridgeChainId::EthSepolia as u8,
472 payload: vec![0].into(),
473 }
474 );
475 Ok(())
476 }
477
478 #[test]
479 fn test_eth_message_conversion_update_blocklist_action_regression() -> anyhow::Result<()> {
480 telemetry_subscribers::init_for_testing();
481 let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
482 &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
483 .unwrap(),
484 )
485 .unwrap();
486 let action = BlocklistCommitteeAction {
487 nonce: 0,
488 chain_id: BridgeChainId::EthSepolia,
489 blocklist_type: BlocklistType::Blocklist,
490 members_to_update: vec![pub_key_bytes],
491 };
492 let message: eth_bridge_committee::BridgeUtils::Message = action.try_into().unwrap();
493 assert_eq!(
494 message,
495 eth_bridge_committee::BridgeUtils::Message {
496 messageType: BridgeActionType::UpdateCommitteeBlocklist as u8,
497 version: COMMITTEE_BLOCKLIST_MESSAGE_VERSION,
498 nonce: 0,
499 chainID: BridgeChainId::EthSepolia as u8,
500 payload: Hex::decode("000168b43fd906c0b8f024a18c56e06744f7c6157c65")
501 .unwrap()
502 .into(),
503 }
504 );
505 Ok(())
506 }
507
508 #[test]
509 fn test_eth_message_conversion_update_limit_action_regression() -> anyhow::Result<()> {
510 telemetry_subscribers::init_for_testing();
511 let action = LimitUpdateAction {
512 nonce: 2,
513 chain_id: BridgeChainId::EthSepolia,
514 sending_chain_id: BridgeChainId::SuiTestnet,
515 new_usd_limit: 4200000,
516 };
517 let message: eth_bridge_limiter::BridgeUtils::Message = action.try_into().unwrap();
518 assert_eq!(
519 message,
520 eth_bridge_limiter::BridgeUtils::Message {
521 messageType: BridgeActionType::LimitUpdate as u8,
522 version: LIMIT_UPDATE_MESSAGE_VERSION,
523 nonce: 2,
524 chainID: BridgeChainId::EthSepolia as u8,
525 payload: Hex::decode("010000000000401640").unwrap().into(),
526 }
527 );
528 Ok(())
529 }
530
531 #[test]
532 fn test_eth_message_conversion_contract_upgrade_action_regression() -> anyhow::Result<()> {
533 telemetry_subscribers::init_for_testing();
534 let action = EvmContractUpgradeAction {
535 nonce: 2,
536 chain_id: BridgeChainId::EthSepolia,
537 proxy_address: EthAddress::repeat_byte(1),
538 new_impl_address: EthAddress::repeat_byte(2),
539 call_data: Vec::from("deadbeef"),
540 };
541 let message: eth_committee_upgradeable_contract::BridgeUtils::Message =
542 action.try_into().unwrap();
543 assert_eq!(
544 message,
545 eth_committee_upgradeable_contract::BridgeUtils::Message {
546 messageType: BridgeActionType::EvmContractUpgrade as u8,
547 version: EVM_CONTRACT_UPGRADE_MESSAGE_VERSION,
548 nonce: 2,
549 chainID: BridgeChainId::EthSepolia as u8,
550 payload: Hex::decode("0x00000000000000000000000001010101010101010101010101010101010101010000000000000000000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000").unwrap().into(),
551 }
552 );
553 Ok(())
554 }
555
556 #[test]
557 fn test_eth_message_conversion_update_price_action_regression() -> anyhow::Result<()> {
558 telemetry_subscribers::init_for_testing();
559 let action = AssetPriceUpdateAction {
560 nonce: 2,
561 chain_id: BridgeChainId::EthSepolia,
562 token_id: TOKEN_ID_ETH,
563 new_usd_price: 80000000,
564 };
565 let message: eth_bridge_config::BridgeUtils::Message = action.try_into().unwrap();
566 assert_eq!(
567 message,
568 eth_bridge_config::BridgeUtils::Message {
569 messageType: BridgeActionType::AssetPriceUpdate as u8,
570 version: ASSET_PRICE_UPDATE_MESSAGE_VERSION,
571 nonce: 2,
572 chainID: BridgeChainId::EthSepolia as u8,
573 payload: Hex::decode("020000000004c4b400").unwrap().into(),
574 }
575 );
576 Ok(())
577 }
578
579 #[test]
580 fn test_eth_message_conversion_add_tokens_on_evm_action_regression() -> anyhow::Result<()> {
581 let action = AddTokensOnEvmAction {
582 nonce: 5,
583 chain_id: BridgeChainId::EthCustom,
584 native: true,
585 token_ids: vec![99, 100, 101],
586 token_addresses: vec![
587 EthAddress::repeat_byte(1),
588 EthAddress::repeat_byte(2),
589 EthAddress::repeat_byte(3),
590 ],
591 token_sui_decimals: vec![5, 6, 7],
592 token_prices: vec![1_000_000_000, 2_000_000_000, 3_000_000_000],
593 };
594 let message: eth_bridge_config::BridgeUtils::Message = action.try_into().unwrap();
595 assert_eq!(
596 message,
597 eth_bridge_config::BridgeUtils::Message {
598 messageType: BridgeActionType::AddTokensOnEvm as u8,
599 version: ADD_TOKENS_ON_EVM_MESSAGE_VERSION,
600 nonce: 5,
601 chainID: BridgeChainId::EthCustom as u8,
602 payload: Hex::decode("0103636465030101010101010101010101010101010101010101020202020202020202020202020202020202020203030303030303030303030303030303030303030305060703000000003b9aca00000000007735940000000000b2d05e00").unwrap().into(),
603 }
604 );
605 Ok(())
606 }
607
608 #[test]
609 fn test_token_deposit_eth_log_to_sui_bridge_event_regression() -> anyhow::Result<()> {
610 telemetry_subscribers::init_for_testing();
611 let tx_hash = TxHash::random();
612 let topics: Vec<B256> = vec![
613 hex!("a0f1d54820817ede8517e70a3d0a9197c015471c5360d2119b759f0359858ce6").into(),
614 hex!("000000000000000000000000000000000000000000000000000000000000000c").into(),
615 hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
616 hex!("0000000000000000000000000000000000000000000000000000000000000002").into(),
617 ];
618 let encoded =
619 Hex::decode("0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000fa56ea0000000000000000000000000014dc79964da2c08b23698b3d3cc7ca32193d9955000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000203b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77").unwrap();
620
621 let log_data = LogData::new(topics, encoded.into()).unwrap();
622 let action = EthLog {
623 block_number: 33,
624 tx_hash,
625 log_index_in_tx: 1,
626 log: Log {
627 inner: alloy::primitives::Log {
628 address: EthAddress::repeat_byte(1),
629 data: log_data,
630 },
631 block_hash: None,
632 block_number: None,
633 transaction_hash: Some(tx_hash),
634 transaction_index: Some(0),
635 log_index: Some(1),
636 ..Default::default()
637 },
638 };
639 let event = EthBridgeEvent::try_from_eth_log(&action).unwrap();
640 assert_eq!(
641 event,
642 EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDeposited(
643 TokensDeposited {
644 sourceChainID: 12,
645 nonce: 0,
646 destinationChainID: 2,
647 tokenID: 2,
648 suiAdjustedAmount: 4200000000,
649 senderAddress: EthAddress::from_str(
650 "0x14dc79964da2c08b23698b3d3cc7ca32193d9955"
651 )
652 .unwrap(),
653 recipientAddress: Bytes::from(
654 Hex::decode(
655 "0x3b1eb23133e94d08d0da9303cfd38e7d4f8f6951f235daa62cd64ea5b6d96d77"
656 )
657 .unwrap(),
658 ),
659 }
660 ))
661 );
662 Ok(())
663 }
664
665 #[test]
666 fn test_0_sui_amount_conversion_for_eth_event() {
667 let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDeposited(
668 TokensDeposited {
669 sourceChainID: BridgeChainId::EthSepolia as u8,
670 nonce: 0,
671 destinationChainID: BridgeChainId::SuiTestnet as u8,
672 tokenID: 2,
673 suiAdjustedAmount: 1,
674 senderAddress: EthAddress::random(),
675 recipientAddress: Bytes::from(SuiAddress::random_for_testing_only().to_vec()),
676 },
677 ));
678 assert!(
679 e.try_into_bridge_action(TxHash::random(), 0)
680 .unwrap()
681 .is_some()
682 );
683
684 let e = EthBridgeEvent::EthSuiBridgeEvents(EthSuiBridgeEvents::TokensDeposited(
685 TokensDeposited {
686 sourceChainID: BridgeChainId::EthSepolia as u8,
687 nonce: 0,
688 destinationChainID: BridgeChainId::SuiTestnet as u8,
689 tokenID: 2,
690 suiAdjustedAmount: 0, senderAddress: EthAddress::random(),
692 recipientAddress: Bytes::from(SuiAddress::random_for_testing_only().to_vec()),
693 },
694 ));
695 match e.try_into_bridge_action(TxHash::random(), 0).unwrap_err() {
696 BridgeError::ZeroValueBridgeTransfer(_) => {}
697 e => panic!("Unexpected error: {:?}", e),
698 }
699 }
700}