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