1#![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#[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 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
56 pub struct $move_struct_name {
57 pub message_key: MoveTypeBridgeMessageKey,
58 }
59
60 #[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#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
99pub struct EmergencyOpEvent {
100 pub frozen: bool,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone)]
105pub struct MoveCommitteeUpdateEvent {
106 pub members: VecMap<Vec<u8>, MoveTypeCommitteeMember>,
107 pub stake_participation_percentage: u64,
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone)]
112pub struct MoveCommitteeMemberUrlUpdateEvent {
113 pub member: Vec<u8>,
114 pub new_url: Vec<u8>,
115}
116
117#[derive(Debug, Serialize, Deserialize, Clone)]
119pub struct MoveBlocklistValidatorEvent {
120 pub blocklisted: bool,
121 pub public_keys: Vec<Vec<u8>>,
122}
123
124#[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#[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#[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#[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#[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#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
210pub struct UpdateTokenPriceEvent {
211 pub token_id: u8,
212 pub new_price: u64,
213}
214
215#[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 pub amount_iota_adjusted: u64,
226}
227
228#[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#[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#[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 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 );
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 impl IotaBridgeEvent {
390 pub fn try_from_iota_event(event: &IotaEvent) -> BridgeResult<Option<IotaBridgeEvent>> {
391 init_all_struct_tags(); $(
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 pub fn get_test_iota_event_and_action(identifier: Identifier) -> (IotaEvent, BridgeAction) {
461 init_all_struct_tags(); 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 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_eq!(mask, 0xF);
545
546 }
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 #[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}