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