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