sui_bridge/
encoding.rs

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