iota_bridge/
encoding.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use enum_dispatch::enum_dispatch;
6use ethers::types::Address as EthAddress;
7use iota_types::base_types::IOTA_ADDRESS_LENGTH;
8
9use crate::types::{
10    AddTokensOnEvmAction, AddTokensOnIotaAction, AssetPriceUpdateAction, BlocklistCommitteeAction,
11    BridgeAction, BridgeActionType, EmergencyAction, EthToIotaBridgeAction,
12    EvmContractUpgradeAction, IotaToEthBridgeAction, LimitUpdateAction,
13};
14
15pub const TOKEN_TRANSFER_MESSAGE_VERSION: u8 = 1;
16pub const COMMITTEE_BLOCKLIST_MESSAGE_VERSION: u8 = 1;
17pub const EMERGENCY_BUTTON_MESSAGE_VERSION: u8 = 1;
18pub const LIMIT_UPDATE_MESSAGE_VERSION: u8 = 1;
19pub const ASSET_PRICE_UPDATE_MESSAGE_VERSION: u8 = 1;
20pub const EVM_CONTRACT_UPGRADE_MESSAGE_VERSION: u8 = 1;
21pub const ADD_TOKENS_ON_IOTA_MESSAGE_VERSION: u8 = 1;
22pub const ADD_TOKENS_ON_EVM_MESSAGE_VERSION: u8 = 1;
23
24pub const BRIDGE_MESSAGE_PREFIX: &[u8] = b"IOTA_BRIDGE_MESSAGE";
25
26/// Encoded bridge message consists of the following fields:
27/// 1. Message type (1 byte)
28/// 2. Message version (1 byte)
29/// 3. Nonce (8 bytes in big endian)
30/// 4. Chain id (1 byte)
31/// 4. Payload (variable length)
32#[enum_dispatch]
33pub trait BridgeMessageEncoding {
34    /// Convert the entire message to bytes
35    fn as_bytes(&self) -> Vec<u8>;
36    /// Convert the payload piece to bytes
37    fn as_payload_bytes(&self) -> Vec<u8>;
38}
39
40impl BridgeMessageEncoding for IotaToEthBridgeAction {
41    fn as_bytes(&self) -> Vec<u8> {
42        let mut bytes = Vec::new();
43        let e = &self.iota_bridge_event;
44        // Add message type
45        bytes.push(BridgeActionType::TokenTransfer as u8);
46        // Add message version
47        bytes.push(TOKEN_TRANSFER_MESSAGE_VERSION);
48        // Add nonce
49        bytes.extend_from_slice(&e.nonce.to_be_bytes());
50        // Add source chain id
51        bytes.push(e.iota_chain_id as u8);
52
53        // Add payload bytes
54        bytes.extend_from_slice(&self.as_payload_bytes());
55
56        bytes
57    }
58
59    fn as_payload_bytes(&self) -> Vec<u8> {
60        let mut bytes = Vec::new();
61        let e = &self.iota_bridge_event;
62
63        // Add source address length
64        bytes.push(IOTA_ADDRESS_LENGTH as u8);
65        // Add source address
66        bytes.extend_from_slice(&e.iota_address.to_vec());
67        // Add dest chain id
68        bytes.push(e.eth_chain_id as u8);
69        // Add dest address length
70        bytes.push(EthAddress::len_bytes() as u8);
71        // Add dest address
72        bytes.extend_from_slice(e.eth_address.as_bytes());
73
74        // Add token id
75        bytes.push(e.token_id);
76
77        // Add token amount
78        bytes.extend_from_slice(&e.amount_iota_adjusted.to_be_bytes());
79
80        bytes
81    }
82}
83
84impl BridgeMessageEncoding for EthToIotaBridgeAction {
85    fn as_bytes(&self) -> Vec<u8> {
86        let mut bytes = Vec::new();
87        let e = &self.eth_bridge_event;
88        // Add message type
89        bytes.push(BridgeActionType::TokenTransfer as u8);
90        // Add message version
91        bytes.push(TOKEN_TRANSFER_MESSAGE_VERSION);
92        // Add nonce
93        bytes.extend_from_slice(&e.nonce.to_be_bytes());
94        // Add source chain id
95        bytes.push(e.eth_chain_id as u8);
96
97        // Add payload bytes
98        bytes.extend_from_slice(&self.as_payload_bytes());
99
100        bytes
101    }
102
103    fn as_payload_bytes(&self) -> Vec<u8> {
104        let mut bytes = Vec::new();
105        let e = &self.eth_bridge_event;
106
107        // Add source address length
108        bytes.push(EthAddress::len_bytes() as u8);
109        // Add source address
110        bytes.extend_from_slice(e.eth_address.as_bytes());
111        // Add dest chain id
112        bytes.push(e.iota_chain_id as u8);
113        // Add dest address length
114        bytes.push(IOTA_ADDRESS_LENGTH as u8);
115        // Add dest address
116        bytes.extend_from_slice(&e.iota_address.to_vec());
117
118        // Add token id
119        bytes.push(e.token_id);
120
121        // Add token amount
122        bytes.extend_from_slice(&e.iota_adjusted_amount.to_be_bytes());
123
124        bytes
125    }
126}
127
128impl BridgeMessageEncoding for BlocklistCommitteeAction {
129    fn as_bytes(&self) -> Vec<u8> {
130        let mut bytes = Vec::new();
131        // Add message type
132        bytes.push(BridgeActionType::UpdateCommitteeBlocklist as u8);
133        // Add message version
134        bytes.push(COMMITTEE_BLOCKLIST_MESSAGE_VERSION);
135        // Add nonce
136        bytes.extend_from_slice(&self.nonce.to_be_bytes());
137        // Add chain id
138        bytes.push(self.chain_id as u8);
139
140        // Add payload bytes
141        bytes.extend_from_slice(&self.as_payload_bytes());
142
143        bytes
144    }
145
146    fn as_payload_bytes(&self) -> Vec<u8> {
147        let mut bytes = Vec::new();
148
149        // Add blocklist type
150        bytes.push(self.blocklist_type as u8);
151        // Add length of updated members.
152        // Unwrap: It should not overflow given what we have today.
153        bytes.push(u8::try_from(self.members_to_update.len()).unwrap());
154
155        // Add list of updated members
156        // Members are represented as pubkey derived evm addresses (20 bytes)
157        let members_bytes = self
158            .members_to_update
159            .iter()
160            .map(|m| m.to_eth_address().to_fixed_bytes().to_vec())
161            .collect::<Vec<_>>();
162        for members_bytes in members_bytes {
163            bytes.extend_from_slice(&members_bytes);
164        }
165
166        bytes
167    }
168}
169
170impl BridgeMessageEncoding for EmergencyAction {
171    fn as_bytes(&self) -> Vec<u8> {
172        let mut bytes = Vec::new();
173        // Add message type
174        bytes.push(BridgeActionType::EmergencyButton as u8);
175        // Add message version
176        bytes.push(EMERGENCY_BUTTON_MESSAGE_VERSION);
177        // Add nonce
178        bytes.extend_from_slice(&self.nonce.to_be_bytes());
179        // Add chain id
180        bytes.push(self.chain_id as u8);
181
182        // Add payload bytes
183        bytes.extend_from_slice(&self.as_payload_bytes());
184
185        bytes
186    }
187
188    fn as_payload_bytes(&self) -> Vec<u8> {
189        vec![self.action_type as u8]
190    }
191}
192
193impl BridgeMessageEncoding for LimitUpdateAction {
194    fn as_bytes(&self) -> Vec<u8> {
195        let mut bytes = Vec::new();
196        // Add message type
197        bytes.push(BridgeActionType::LimitUpdate as u8);
198        // Add message version
199        bytes.push(LIMIT_UPDATE_MESSAGE_VERSION);
200        // Add nonce
201        bytes.extend_from_slice(&self.nonce.to_be_bytes());
202        // Add chain id
203        bytes.push(self.chain_id as u8);
204
205        // Add payload bytes
206        bytes.extend_from_slice(&self.as_payload_bytes());
207
208        bytes
209    }
210
211    fn as_payload_bytes(&self) -> Vec<u8> {
212        let mut bytes = Vec::new();
213        // Add sending chain id
214        bytes.push(self.sending_chain_id as u8);
215        // Add new usd limit
216        bytes.extend_from_slice(&self.new_usd_limit.to_be_bytes());
217        bytes
218    }
219}
220
221impl BridgeMessageEncoding for AssetPriceUpdateAction {
222    fn as_bytes(&self) -> Vec<u8> {
223        let mut bytes = Vec::new();
224        // Add message type
225        bytes.push(BridgeActionType::AssetPriceUpdate as u8);
226        // Add message version
227        bytes.push(EMERGENCY_BUTTON_MESSAGE_VERSION);
228        // Add nonce
229        bytes.extend_from_slice(&self.nonce.to_be_bytes());
230        // Add chain id
231        bytes.push(self.chain_id as u8);
232
233        // Add payload bytes
234        bytes.extend_from_slice(&self.as_payload_bytes());
235
236        bytes
237    }
238
239    fn as_payload_bytes(&self) -> Vec<u8> {
240        let mut bytes = Vec::new();
241        // Add token id
242        bytes.push(self.token_id);
243        // Add new usd limit
244        bytes.extend_from_slice(&self.new_usd_price.to_be_bytes());
245        bytes
246    }
247}
248
249impl BridgeMessageEncoding for EvmContractUpgradeAction {
250    fn as_bytes(&self) -> Vec<u8> {
251        let mut bytes = Vec::new();
252        // Add message type
253        bytes.push(BridgeActionType::EvmContractUpgrade as u8);
254        // Add message version
255        bytes.push(EVM_CONTRACT_UPGRADE_MESSAGE_VERSION);
256        // Add nonce
257        bytes.extend_from_slice(&self.nonce.to_be_bytes());
258        // Add chain id
259        bytes.push(self.chain_id as u8);
260
261        // Add payload bytes
262        bytes.extend_from_slice(&self.as_payload_bytes());
263
264        bytes
265    }
266
267    fn as_payload_bytes(&self) -> Vec<u8> {
268        ethers::abi::encode(&[
269            ethers::abi::Token::Address(self.proxy_address),
270            ethers::abi::Token::Address(self.new_impl_address),
271            ethers::abi::Token::Bytes(self.call_data.clone()),
272        ])
273    }
274}
275
276impl BridgeMessageEncoding for AddTokensOnIotaAction {
277    fn as_bytes(&self) -> Vec<u8> {
278        let mut bytes = Vec::new();
279        // Add message type
280        bytes.push(BridgeActionType::AddTokensOnIota as u8);
281        // Add message version
282        bytes.push(ADD_TOKENS_ON_IOTA_MESSAGE_VERSION);
283        // Add nonce
284        bytes.extend_from_slice(&self.nonce.to_be_bytes());
285        // Add chain id
286        bytes.push(self.chain_id as u8);
287
288        // Add payload bytes
289        bytes.extend_from_slice(&self.as_payload_bytes());
290
291        bytes
292    }
293
294    fn as_payload_bytes(&self) -> Vec<u8> {
295        let mut bytes = Vec::new();
296        // Add native
297        bytes.push(self.native as u8);
298        // Add token ids
299        // Unwrap: bcs serialization should not fail
300        bytes.extend_from_slice(&bcs::to_bytes(&self.token_ids).unwrap());
301
302        // Add token type names
303        // Unwrap: bcs serialization should not fail
304        bytes.extend_from_slice(
305            &bcs::to_bytes(
306                &self
307                    .token_type_names
308                    .iter()
309                    .map(|m| m.to_canonical_string(false))
310                    .collect::<Vec<_>>(),
311            )
312            .unwrap(),
313        );
314
315        // Add token prices
316        // Unwrap: bcs serialization should not fail
317        bytes.extend_from_slice(&bcs::to_bytes(&self.token_prices).unwrap());
318
319        bytes
320    }
321}
322
323impl BridgeMessageEncoding for AddTokensOnEvmAction {
324    fn as_bytes(&self) -> Vec<u8> {
325        let mut bytes = Vec::new();
326        // Add message type
327        bytes.push(BridgeActionType::AddTokensOnEvm as u8);
328        // Add message version
329        bytes.push(ADD_TOKENS_ON_EVM_MESSAGE_VERSION);
330        // Add nonce
331        bytes.extend_from_slice(&self.nonce.to_be_bytes());
332        // Add chain id
333        bytes.push(self.chain_id as u8);
334
335        // Add payload bytes
336        bytes.extend_from_slice(&self.as_payload_bytes());
337
338        bytes
339    }
340
341    fn as_payload_bytes(&self) -> Vec<u8> {
342        let mut bytes = Vec::new();
343        // Add native
344        bytes.push(self.native as u8);
345        // Add token ids
346        // Unwrap: bcs serialization should not fail
347        bytes.push(u8::try_from(self.token_ids.len()).unwrap());
348        for token_id in &self.token_ids {
349            bytes.push(*token_id);
350        }
351
352        // Add token addresses
353        // Unwrap: bcs serialization should not fail
354        bytes.push(u8::try_from(self.token_addresses.len()).unwrap());
355        for token_address in &self.token_addresses {
356            bytes.extend_from_slice(&token_address.to_fixed_bytes());
357        }
358
359        // Add token iota decimals
360        // Unwrap: bcs serialization should not fail
361        bytes.push(u8::try_from(self.token_iota_decimals.len()).unwrap());
362        for token_iota_decimal in &self.token_iota_decimals {
363            bytes.push(*token_iota_decimal);
364        }
365
366        // Add token prices
367        // Unwrap: bcs serialization should not fail
368        bytes.push(u8::try_from(self.token_prices.len()).unwrap());
369        for token_price in &self.token_prices {
370            bytes.extend_from_slice(&token_price.to_be_bytes());
371        }
372        bytes
373    }
374}
375
376impl BridgeAction {
377    /// Convert to message bytes to verify in Move and Solidity
378    pub fn to_bytes(&self) -> Vec<u8> {
379        let mut bytes = Vec::new();
380        // Add prefix
381        bytes.extend_from_slice(BRIDGE_MESSAGE_PREFIX);
382        // Add bytes from message itself
383        bytes.extend_from_slice(&self.as_bytes());
384        bytes
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use std::str::FromStr;
391
392    use ethers::{
393        abi::ParamType,
394        types::{Address as EthAddress, TxHash},
395    };
396    use fastcrypto::{
397        encoding::{Encoding, Hex},
398        hash::{HashFunction, Keccak256},
399        traits::ToFromBytes,
400    };
401    use iota_types::{
402        TypeTag,
403        base_types::{IotaAddress, TransactionDigest},
404        bridge::{BridgeChainId, TOKEN_ID_BTC, TOKEN_ID_USDC},
405    };
406    use prometheus::Registry;
407
408    use super::*;
409    use crate::{
410        abi::EthToIotaTokenBridgeV1,
411        crypto::{BridgeAuthorityKeyPair, BridgeAuthorityPublicKeyBytes, BridgeAuthoritySignInfo},
412        events::EmittedIotaToEthTokenBridgeV1,
413        types::{BlocklistType, EmergencyActionType, USD_MULTIPLIER},
414    };
415
416    #[test]
417    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
418    fn test_bridge_message_encoding() -> anyhow::Result<()> {
419        telemetry_subscribers::init_for_testing();
420        let registry = Registry::new();
421        iota_metrics::init_metrics(&registry);
422        let nonce = 54321u64;
423        let iota_tx_digest = TransactionDigest::random();
424        let iota_chain_id = BridgeChainId::IotaTestnet;
425        let iota_tx_event_index = 1u16;
426        let eth_chain_id = BridgeChainId::EthSepolia;
427        let iota_address = IotaAddress::random_for_testing_only();
428        let eth_address = EthAddress::random();
429        let token_id = TOKEN_ID_USDC;
430        let amount_iota_adjusted = 1_000_000;
431
432        let iota_bridge_event = EmittedIotaToEthTokenBridgeV1 {
433            nonce,
434            iota_chain_id,
435            eth_chain_id,
436            iota_address,
437            eth_address,
438            token_id,
439            amount_iota_adjusted,
440        };
441
442        let encoded_bytes = BridgeAction::IotaToEthBridgeAction(IotaToEthBridgeAction {
443            iota_tx_digest,
444            iota_tx_event_index,
445            iota_bridge_event,
446        })
447        .to_bytes();
448
449        // Construct the expected bytes
450        let prefix_bytes = BRIDGE_MESSAGE_PREFIX.to_vec(); // len: 18
451        let message_type = vec![BridgeActionType::TokenTransfer as u8]; // len: 1
452        let message_version = vec![TOKEN_TRANSFER_MESSAGE_VERSION]; // len: 1
453        let nonce_bytes = nonce.to_be_bytes().to_vec(); // len: 8
454        let source_chain_id_bytes = vec![iota_chain_id as u8]; // len: 1
455
456        let iota_address_length_bytes = vec![IOTA_ADDRESS_LENGTH as u8]; // len: 1
457        let iota_address_bytes = iota_address.to_vec(); // len: 32
458        let dest_chain_id_bytes = vec![eth_chain_id as u8]; // len: 1
459        let eth_address_length_bytes = vec![EthAddress::len_bytes() as u8]; // len: 1
460        let eth_address_bytes = eth_address.as_bytes().to_vec(); // len: 20
461
462        let token_id_bytes = vec![token_id]; // len: 1
463        let token_amount_bytes = amount_iota_adjusted.to_be_bytes().to_vec(); // len: 8
464
465        let mut combined_bytes = Vec::new();
466        combined_bytes.extend_from_slice(&prefix_bytes);
467        combined_bytes.extend_from_slice(&message_type);
468        combined_bytes.extend_from_slice(&message_version);
469        combined_bytes.extend_from_slice(&nonce_bytes);
470        combined_bytes.extend_from_slice(&source_chain_id_bytes);
471        combined_bytes.extend_from_slice(&iota_address_length_bytes);
472        combined_bytes.extend_from_slice(&iota_address_bytes);
473        combined_bytes.extend_from_slice(&dest_chain_id_bytes);
474        combined_bytes.extend_from_slice(&eth_address_length_bytes);
475        combined_bytes.extend_from_slice(&eth_address_bytes);
476        combined_bytes.extend_from_slice(&token_id_bytes);
477        combined_bytes.extend_from_slice(&token_amount_bytes);
478
479        assert_eq!(combined_bytes, encoded_bytes);
480
481        // Assert fixed length
482        // TODO: for each action type add a test to assert the length
483        assert_eq!(
484            combined_bytes.len(),
485            18 + 1 + 1 + 8 + 1 + 1 + 32 + 1 + 20 + 1 + 1 + 8
486        );
487        Ok(())
488    }
489
490    #[test]
491    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
492    fn test_bridge_message_encoding_regression_emitted_iota_to_eth_token_bridge_v1()
493    -> anyhow::Result<()> {
494        telemetry_subscribers::init_for_testing();
495        let registry = Registry::new();
496        iota_metrics::init_metrics(&registry);
497        let iota_tx_digest = TransactionDigest::random();
498        let iota_tx_event_index = 1u16;
499
500        let nonce = 10u64;
501        let iota_chain_id = BridgeChainId::IotaTestnet;
502        let eth_chain_id = BridgeChainId::EthSepolia;
503        let iota_address = IotaAddress::from_str(
504            "0x0000000000000000000000000000000000000000000000000000000000000064",
505        )
506        .unwrap();
507        let eth_address =
508            EthAddress::from_str("0x00000000000000000000000000000000000000c8").unwrap();
509        let token_id = TOKEN_ID_USDC;
510        let amount_iota_adjusted = 12345;
511
512        let iota_bridge_event = EmittedIotaToEthTokenBridgeV1 {
513            nonce,
514            iota_chain_id,
515            eth_chain_id,
516            iota_address,
517            eth_address,
518            token_id,
519            amount_iota_adjusted,
520        };
521        let encoded_bytes = BridgeAction::IotaToEthBridgeAction(IotaToEthBridgeAction {
522            iota_tx_digest,
523            iota_tx_event_index,
524            iota_bridge_event,
525        })
526        .to_bytes();
527        assert_eq!(
528            encoded_bytes,
529            Hex::decode("5355495f4252494447455f4d4553534147450001000000000000000a012000000000000000000000000000000000000000000000000000000000000000640b1400000000000000000000000000000000000000c8030000000000003039").unwrap(),
530        );
531
532        let hash = Keccak256::digest(encoded_bytes).digest;
533        assert_eq!(
534            hash.to_vec(),
535            Hex::decode("6ab34c52b6264cbc12fe8c3874f9b08f8481d2e81530d136386646dbe2f8baf4")
536                .unwrap(),
537        );
538        Ok(())
539    }
540
541    #[test]
542    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
543    fn test_bridge_message_encoding_blocklist_update_v1() {
544        telemetry_subscribers::init_for_testing();
545        let registry = Registry::new();
546        iota_metrics::init_metrics(&registry);
547
548        let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
549            &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
550                .unwrap(),
551        )
552        .unwrap();
553        let blocklist_action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
554            nonce: 129,
555            chain_id: BridgeChainId::IotaCustom,
556            blocklist_type: BlocklistType::Blocklist,
557            members_to_update: vec![pub_key_bytes.clone()],
558        });
559        let bytes = blocklist_action.to_bytes();
560        // 5355495f4252494447455f4d455353414745: prefix
561        // 01: msg type
562        // 01: msg version
563        // 0000000000000081: nonce
564        // 03: chain id
565        // 00: blocklist type
566        // 01: length of updated members
567        // [
568        // 68b43fd906c0b8f024a18c56e06744f7c6157c65
569        // ]: blocklisted members abi-encoded
570        assert_eq!(bytes, Hex::decode("5355495f4252494447455f4d4553534147450101000000000000008102000168b43fd906c0b8f024a18c56e06744f7c6157c65").unwrap());
571
572        let pub_key_bytes_2 = BridgeAuthorityPublicKeyBytes::from_bytes(
573            &Hex::decode("027f1178ff417fc9f5b8290bd8876f0a157a505a6c52db100a8492203ddd1d4279")
574                .unwrap(),
575        )
576        .unwrap();
577        // its evm address: 0xacaef39832cb995c4e049437a3e2ec6a7bad1ab5
578        let blocklist_action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
579            nonce: 68,
580            chain_id: BridgeChainId::IotaCustom,
581            blocklist_type: BlocklistType::Unblocklist,
582            members_to_update: vec![pub_key_bytes.clone(), pub_key_bytes_2.clone()],
583        });
584        let bytes = blocklist_action.to_bytes();
585        // 5355495f4252494447455f4d455353414745: prefix
586        // 01: msg type
587        // 01: msg version
588        // 0000000000000044: nonce
589        // 02: chain id
590        // 01: blocklist type
591        // 02: length of updated members
592        // [
593        // 68b43fd906c0b8f024a18c56e06744f7c6157c65
594        // acaef39832cb995c4e049437a3e2ec6a7bad1ab5
595        // ]: blocklisted members abi-encoded
596        assert_eq!(bytes, Hex::decode("5355495f4252494447455f4d4553534147450101000000000000004402010268b43fd906c0b8f024a18c56e06744f7c6157c65acaef39832cb995c4e049437a3e2ec6a7bad1ab5").unwrap());
597
598        let blocklist_action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
599            nonce: 49,
600            chain_id: BridgeChainId::EthCustom,
601            blocklist_type: BlocklistType::Blocklist,
602            members_to_update: vec![pub_key_bytes.clone()],
603        });
604        let bytes = blocklist_action.to_bytes();
605        // 5355495f4252494447455f4d455353414745: prefix
606        // 01: msg type
607        // 01: msg version
608        // 0000000000000031: nonce
609        // 0c: chain id
610        // 00: blocklist type
611        // 01: length of updated members
612        // [
613        // 68b43fd906c0b8f024a18c56e06744f7c6157c65
614        // ]: blocklisted members abi-encoded
615        assert_eq!(bytes, Hex::decode("5355495f4252494447455f4d455353414745010100000000000000310c000168b43fd906c0b8f024a18c56e06744f7c6157c65").unwrap());
616
617        let blocklist_action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
618            nonce: 94,
619            chain_id: BridgeChainId::EthSepolia,
620            blocklist_type: BlocklistType::Unblocklist,
621            members_to_update: vec![pub_key_bytes.clone(), pub_key_bytes_2.clone()],
622        });
623        let bytes = blocklist_action.to_bytes();
624        // 5355495f4252494447455f4d455353414745: prefix
625        // 01: msg type
626        // 01: msg version
627        // 000000000000005e: nonce
628        // 0b: chain id
629        // 01: blocklist type
630        // 02: length of updated members
631        // [
632        // 00000000000000000000000068b43fd906c0b8f024a18c56e06744f7c6157c65
633        // 000000000000000000000000acaef39832cb995c4e049437a3e2ec6a7bad1ab5
634        // ]: blocklisted members abi-encoded
635        assert_eq!(bytes, Hex::decode("5355495f4252494447455f4d4553534147450101000000000000005e0b010268b43fd906c0b8f024a18c56e06744f7c6157c65acaef39832cb995c4e049437a3e2ec6a7bad1ab5").unwrap());
636    }
637
638    #[test]
639    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
640    fn test_bridge_message_encoding_emergency_action() {
641        let action = BridgeAction::EmergencyAction(EmergencyAction {
642            nonce: 55,
643            chain_id: BridgeChainId::IotaCustom,
644            action_type: EmergencyActionType::Pause,
645        });
646        let bytes = action.to_bytes();
647        // 5355495f4252494447455f4d455353414745: prefix
648        // 02: msg type
649        // 01: msg version
650        // 0000000000000037: nonce
651        // 03: chain id
652        // 00: action type
653        assert_eq!(
654            bytes,
655            Hex::decode("5355495f4252494447455f4d455353414745020100000000000000370200").unwrap()
656        );
657
658        let action = BridgeAction::EmergencyAction(EmergencyAction {
659            nonce: 56,
660            chain_id: BridgeChainId::EthSepolia,
661            action_type: EmergencyActionType::Unpause,
662        });
663        let bytes = action.to_bytes();
664        // 5355495f4252494447455f4d455353414745: prefix
665        // 02: msg type
666        // 01: msg version
667        // 0000000000000038: nonce
668        // 0b: chain id
669        // 01: action type
670        assert_eq!(
671            bytes,
672            Hex::decode("5355495f4252494447455f4d455353414745020100000000000000380b01").unwrap()
673        );
674    }
675
676    #[test]
677    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
678    fn test_bridge_message_encoding_limit_update_action() {
679        let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
680            nonce: 15,
681            chain_id: BridgeChainId::IotaCustom,
682            sending_chain_id: BridgeChainId::EthCustom,
683            new_usd_limit: 1_000_000 * USD_MULTIPLIER, // $1M USD
684        });
685        let bytes = action.to_bytes();
686        // 5355495f4252494447455f4d455353414745: prefix
687        // 03: msg type
688        // 01: msg version
689        // 000000000000000f: nonce
690        // 03: chain id
691        // 0c: sending chain id
692        // 00000002540be400: new usd limit
693        assert_eq!(
694            bytes,
695            Hex::decode(
696                "5355495f4252494447455f4d4553534147450301000000000000000f020c00000002540be400"
697            )
698            .unwrap()
699        );
700    }
701
702    #[test]
703    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
704    fn test_bridge_message_encoding_asset_price_update_action() {
705        let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
706            nonce: 266,
707            chain_id: BridgeChainId::IotaCustom,
708            token_id: TOKEN_ID_BTC,
709            new_usd_price: 100_000 * USD_MULTIPLIER, // $100k USD
710        });
711        let bytes = action.to_bytes();
712        // 5355495f4252494447455f4d455353414745: prefix
713        // 04: msg type
714        // 01: msg version
715        // 000000000000010a: nonce
716        // 03: chain id
717        // 01: token id
718        // 000000003b9aca00: new usd price
719        assert_eq!(
720            bytes,
721            Hex::decode(
722                "5355495f4252494447455f4d4553534147450401000000000000010a0201000000003b9aca00"
723            )
724            .unwrap()
725        );
726    }
727
728    #[test]
729    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
730    fn test_bridge_message_encoding_evm_contract_upgrade_action() {
731        // Calldata with only the function selector and no parameters: `function
732        // initializeV2()`
733        let function_signature = "initializeV2()";
734        let selector = &Keccak256::digest(function_signature).digest[0..4];
735        let call_data = selector.to_vec();
736        assert_eq!(Hex::encode(call_data.clone()), "5cd8a76b");
737
738        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
739            nonce: 123,
740            chain_id: BridgeChainId::EthCustom,
741            proxy_address: EthAddress::repeat_byte(6),
742            new_impl_address: EthAddress::repeat_byte(9),
743            call_data,
744        });
745        // 5355495f4252494447455f4d455353414745: prefix
746        // 05: msg type
747        // 01: msg version
748        // 000000000000007b: nonce
749        // 0c: chain id
750        // 0000000000000000000000000606060606060606060606060606060606060606: proxy
751        // address
752        // 0000000000000000000000000909090909090909090909090909090909090909: new impl
753        // address
754        //
755        // 0000000000000000000000000000000000000000000000000000000000000060
756        // 0000000000000000000000000000000000000000000000000000000000000004
757        // 5cd8a76b00000000000000000000000000000000000000000000000000000000: call data
758        assert_eq!(
759            Hex::encode(action.to_bytes().clone()),
760            "5355495f4252494447455f4d4553534147450501000000000000007b0c00000000000000000000000006060606060606060606060606060606060606060000000000000000000000000909090909090909090909090909090909090909000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000045cd8a76b00000000000000000000000000000000000000000000000000000000"
761        );
762
763        // Calldata with one parameter: `function newMockFunction(bool)`
764        let function_signature = "newMockFunction(bool)";
765        let selector = &Keccak256::digest(function_signature).digest[0..4];
766        let mut call_data = selector.to_vec();
767        call_data.extend(ethers::abi::encode(&[ethers::abi::Token::Bool(true)]));
768        assert_eq!(
769            Hex::encode(call_data.clone()),
770            "417795ef0000000000000000000000000000000000000000000000000000000000000001"
771        );
772        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
773            nonce: 123,
774            chain_id: BridgeChainId::EthCustom,
775            proxy_address: EthAddress::repeat_byte(6),
776            new_impl_address: EthAddress::repeat_byte(9),
777            call_data,
778        });
779        // 5355495f4252494447455f4d455353414745: prefix
780        // 05: msg type
781        // 01: msg version
782        // 000000000000007b: nonce
783        // 0c: chain id
784        // 0000000000000000000000000606060606060606060606060606060606060606: proxy
785        // address
786        // 0000000000000000000000000909090909090909090909090909090909090909: new impl
787        // address
788        //
789        // 0000000000000000000000000000000000000000000000000000000000000060
790        // 0000000000000000000000000000000000000000000000000000000000000024
791        // 417795ef00000000000000000000000000000000000000000000000000000000
792        // 0000000100000000000000000000000000000000000000000000000000000000: call data
793        assert_eq!(
794            Hex::encode(action.to_bytes().clone()),
795            "5355495f4252494447455f4d4553534147450501000000000000007b0c0000000000000000000000000606060606060606060606060606060606060606000000000000000000000000090909090909090909090909090909090909090900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024417795ef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000"
796        );
797
798        // Calldata with two parameters: `function newerMockFunction(bool, uint8)`
799        let function_signature = "newMockFunction(bool,uint8)";
800        let selector = &Keccak256::digest(function_signature).digest[0..4];
801        let mut call_data = selector.to_vec();
802        call_data.extend(ethers::abi::encode(&[
803            ethers::abi::Token::Bool(true),
804            ethers::abi::Token::Uint(42u8.into()),
805        ]));
806        assert_eq!(
807            Hex::encode(call_data.clone()),
808            "be8fc25d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002a"
809        );
810        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
811            nonce: 123,
812            chain_id: BridgeChainId::EthCustom,
813            proxy_address: EthAddress::repeat_byte(6),
814            new_impl_address: EthAddress::repeat_byte(9),
815            call_data,
816        });
817        // 5355495f4252494447455f4d455353414745: prefix
818        // 05: msg type
819        // 01: msg version
820        // 000000000000007b: nonce
821        // 0c: chain id
822        // 0000000000000000000000000606060606060606060606060606060606060606: proxy
823        // address
824        // 0000000000000000000000000909090909090909090909090909090909090909: new impl
825        // address
826        //
827        // 0000000000000000000000000000000000000000000000000000000000000060
828        // 0000000000000000000000000000000000000000000000000000000000000044
829        // be8fc25d00000000000000000000000000000000000000000000000000000000
830        // 0000000100000000000000000000000000000000000000000000000000000000
831        // 0000002a00000000000000000000000000000000000000000000000000000000: call data
832        assert_eq!(
833            Hex::encode(action.to_bytes().clone()),
834            "5355495f4252494447455f4d4553534147450501000000000000007b0c0000000000000000000000000606060606060606060606060606060606060606000000000000000000000000090909090909090909090909090909090909090900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044be8fc25d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000"
835        );
836
837        // Empty calldate
838        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
839            nonce: 123,
840            chain_id: BridgeChainId::EthCustom,
841            proxy_address: EthAddress::repeat_byte(6),
842            new_impl_address: EthAddress::repeat_byte(9),
843            call_data: vec![],
844        });
845        // 5355495f4252494447455f4d455353414745: prefix
846        // 05: msg type
847        // 01: msg version
848        // 000000000000007b: nonce
849        // 0c: chain id
850        // 0000000000000000000000000606060606060606060606060606060606060606: proxy
851        // address
852        // 0000000000000000000000000909090909090909090909090909090909090909: new impl
853        // address
854        //
855        // 0000000000000000000000000000000000000000000000000000000000000060
856        // 0000000000000000000000000000000000000000000000000000000000000000: call data
857        let data = action.to_bytes();
858        assert_eq!(
859            Hex::encode(data.clone()),
860            "5355495f4252494447455f4d4553534147450501000000000000007b0c0000000000000000000000000606060606060606060606060606060606060606000000000000000000000000090909090909090909090909090909090909090900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"
861        );
862        let types = vec![ParamType::Address, ParamType::Address, ParamType::Bytes];
863        // Ensure that the call data (start from bytes 29) can be decoded
864        ethers::abi::decode(&types, &data[29..]).unwrap();
865    }
866
867    #[test]
868    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
869    fn test_bridge_message_encoding_regression_eth_to_iota_token_bridge_v1() -> anyhow::Result<()> {
870        telemetry_subscribers::init_for_testing();
871        let registry = Registry::new();
872        iota_metrics::init_metrics(&registry);
873        let eth_tx_hash = TxHash::random();
874        let eth_event_index = 1u16;
875
876        let nonce = 10u64;
877        let iota_chain_id = BridgeChainId::IotaTestnet;
878        let eth_chain_id = BridgeChainId::EthSepolia;
879        let iota_address = IotaAddress::from_str(
880            "0x0000000000000000000000000000000000000000000000000000000000000064",
881        )
882        .unwrap();
883        let eth_address =
884            EthAddress::from_str("0x00000000000000000000000000000000000000c8").unwrap();
885        let token_id = TOKEN_ID_USDC;
886        let iota_adjusted_amount = 12345;
887
888        let eth_bridge_event = EthToIotaTokenBridgeV1 {
889            nonce,
890            iota_chain_id,
891            eth_chain_id,
892            iota_address,
893            eth_address,
894            token_id,
895            iota_adjusted_amount,
896        };
897        let encoded_bytes = BridgeAction::EthToIotaBridgeAction(EthToIotaBridgeAction {
898            eth_tx_hash,
899            eth_event_index,
900            eth_bridge_event,
901        })
902        .to_bytes();
903
904        assert_eq!(
905            encoded_bytes,
906            Hex::decode("5355495f4252494447455f4d4553534147450001000000000000000a0b1400000000000000000000000000000000000000c801200000000000000000000000000000000000000000000000000000000000000064030000000000003039").unwrap(),
907        );
908
909        let hash = Keccak256::digest(encoded_bytes).digest;
910        assert_eq!(
911            hash.to_vec(),
912            Hex::decode("b352508c301a37bb1b68a75dd0fc42b6f692b2650818631c8f8a4d4d3e5bef46")
913                .unwrap(),
914        );
915        Ok(())
916    }
917
918    #[test]
919    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
920    fn test_bridge_message_encoding_regression_add_coins_on_iota() -> anyhow::Result<()> {
921        telemetry_subscribers::init_for_testing();
922
923        let action = BridgeAction::AddTokensOnIotaAction(AddTokensOnIotaAction {
924            nonce: 0,
925            chain_id: BridgeChainId::IotaCustom,
926            native: false,
927            token_ids: vec![1, 2, 3, 4],
928            token_type_names: vec![
929                TypeTag::from_str("0x9b5e13bcd0cb23ff25c07698e89d48056c745338d8c9dbd033a4172b87027073::btc::BTC").unwrap(),
930                TypeTag::from_str("0x7970d71c03573f540a7157f0d3970e117effa6ae16cefd50b45c749670b24e6a::eth::ETH").unwrap(),
931                TypeTag::from_str("0x500e429a24478405d5130222b20f8570a746b6bc22423f14b4d4e6a8ea580736::usdc::USDC").unwrap(),
932                TypeTag::from_str("0x46bfe51da1bd9511919a92eb1154149b36c0f4212121808e13e3e5857d607a9c::usdt::USDT").unwrap(),
933            ],
934            token_prices: vec![
935                500_000_000u64,
936                30_000_000u64,
937                1_000u64,
938                1_000u64,
939            ]
940        });
941        let encoded_bytes = action.to_bytes();
942
943        assert_eq!(
944            Hex::encode(encoded_bytes),
945            "5355495f4252494447455f4d4553534147450601000000000000000002000401020304044a396235653133626364306362323366663235633037363938653839643438303536633734353333386438633964626430333361343137326238373032373037333a3a6274633a3a4254434a373937306437316330333537336635343061373135376630643339373065313137656666613661653136636566643530623435633734393637306232346536613a3a6574683a3a4554484c353030653432396132343437383430356435313330323232623230663835373061373436623662633232343233663134623464346536613865613538303733363a3a757364633a3a555344434c343662666535316461316264393531313931396139326562313135343134396233366330663432313231323138303865313365336535383537643630376139633a3a757364743a3a55534454040065cd1d0000000080c3c90100000000e803000000000000e803000000000000",
946        );
947        Ok(())
948    }
949
950    #[test]
951    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
952    fn test_bridge_message_encoding_regression_add_coins_on_evm() -> anyhow::Result<()> {
953        let action = BridgeAction::AddTokensOnEvmAction(crate::types::AddTokensOnEvmAction {
954            nonce: 0,
955            chain_id: BridgeChainId::EthCustom,
956            native: true,
957            token_ids: vec![99, 100, 101],
958            token_addresses: vec![
959                EthAddress::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
960                EthAddress::from_str("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84").unwrap(),
961                EthAddress::from_str("0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72").unwrap(),
962            ],
963            token_iota_decimals: vec![5, 6, 7],
964            token_prices: vec![1_000_000_000, 2_000_000_000, 3_000_000_000],
965        });
966        let encoded_bytes = action.to_bytes();
967
968        assert_eq!(
969            Hex::encode(encoded_bytes),
970            "5355495f4252494447455f4d455353414745070100000000000000000c0103636465036b175474e89094c44da98b954eedeac495271d0fae7ab96520de3a18e5e111b5eaab095312d7fe84c18360217d8f7ab5e7c516566761ea12ce7f9d720305060703000000003b9aca00000000007735940000000000b2d05e00",
971        );
972        // To generate regression test for sol contracts
973        let keys = get_bridge_encoding_regression_test_keys();
974        for key in keys {
975            let pub_key = key.public.as_bytes();
976            println!("pub_key: {:?}", Hex::encode(pub_key));
977            println!(
978                "sig: {:?}",
979                Hex::encode(
980                    BridgeAuthoritySignInfo::new(&action, &key)
981                        .signature
982                        .as_bytes()
983                )
984            );
985        }
986        Ok(())
987    }
988
989    fn get_bridge_encoding_regression_test_keys() -> Vec<BridgeAuthorityKeyPair> {
990        vec![
991            BridgeAuthorityKeyPair::from_bytes(
992                &Hex::decode("e42c82337ce12d4a7ad6cd65876d91b2ab6594fd50cdab1737c91773ba7451db")
993                    .unwrap(),
994            )
995            .unwrap(),
996            BridgeAuthorityKeyPair::from_bytes(
997                &Hex::decode("1aacd610da3d0cc691a04b83b01c34c6c65cda0fe8d502df25ff4b3185c85687")
998                    .unwrap(),
999            )
1000            .unwrap(),
1001            BridgeAuthorityKeyPair::from_bytes(
1002                &Hex::decode("53e7baf8378fbc62692e3056c2e10c6666ef8b5b3a53914830f47636d1678140")
1003                    .unwrap(),
1004            )
1005            .unwrap(),
1006            BridgeAuthorityKeyPair::from_bytes(
1007                &Hex::decode("08b5350a091faabd5f25b6e290bfc3f505d43208775b9110dfed5ee6c7a653f0")
1008                    .unwrap(),
1009            )
1010            .unwrap(),
1011        ]
1012    }
1013}