1use crate::abi::{EthToSuiTokenBridgeV1, EthToSuiTokenBridgeV2};
5use crate::crypto::BridgeAuthorityPublicKeyBytes;
6use crate::crypto::{
7 BridgeAuthorityPublicKey, BridgeAuthorityRecoverableSignature, BridgeAuthoritySignInfo,
8};
9use crate::encoding::BridgeMessageEncoding;
10use crate::error::{BridgeError, BridgeResult};
11use crate::events::EmittedSuiToEthTokenBridgeV1;
12use alloy::primitives::{Address as EthAddress, B256, TxHash as EthTransactionHash};
13use alloy::rpc::types::eth::Log;
14use enum_dispatch::enum_dispatch;
15use fastcrypto::encoding::{Encoding, Hex};
16use fastcrypto::hash::{HashFunction, Keccak256};
17use num_enum::TryFromPrimitive;
18use rand::Rng;
19use rand::seq::SliceRandom;
20use serde::{Deserialize, Serialize};
21use shared_crypto::intent::IntentScope;
22use std::collections::{BTreeMap, BTreeSet};
23use std::fmt::Debug;
24use strum_macros::Display;
25use sui_types::TypeTag;
26use sui_types::base_types::SuiAddress;
27use sui_types::bridge::{
28 APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM, APPROVAL_THRESHOLD_ADD_TOKENS_ON_SUI,
29 BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER, BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER, BridgeChainId,
30 MoveTypeBridgeMessage, MoveTypeBridgeRecord, MoveTypeTokenTransferPayload,
31};
32use sui_types::bridge::{
33 APPROVAL_THRESHOLD_ASSET_PRICE_UPDATE, APPROVAL_THRESHOLD_COMMITTEE_BLOCKLIST,
34 APPROVAL_THRESHOLD_EMERGENCY_PAUSE, APPROVAL_THRESHOLD_EMERGENCY_UNPAUSE,
35 APPROVAL_THRESHOLD_EVM_CONTRACT_UPGRADE, APPROVAL_THRESHOLD_LIMIT_UPDATE,
36 APPROVAL_THRESHOLD_TOKEN_TRANSFER, MoveTypeParsedTokenTransferMessage,
37};
38use sui_types::committee::CommitteeTrait;
39use sui_types::committee::StakeUnit;
40use sui_types::crypto::ToFromBytes;
41use sui_types::digests::{Digest, TransactionDigest};
42use sui_types::message_envelope::{Envelope, Message, VerifiedEnvelope};
43
44pub const BRIDGE_AUTHORITY_TOTAL_VOTING_POWER: u64 = 10000;
45
46pub const USD_MULTIPLIER: u64 = 10000; pub type IsBridgePaused = bool;
49pub const BRIDGE_PAUSED: bool = true;
50pub const BRIDGE_UNPAUSED: bool = false;
51
52#[derive(Debug, Eq, PartialEq, Clone)]
53pub struct BridgeAuthority {
54 pub sui_address: SuiAddress,
55 pub pubkey: BridgeAuthorityPublicKey,
56 pub voting_power: u64,
57 pub base_url: String,
58 pub is_blocklisted: bool,
59}
60
61impl BridgeAuthority {
62 pub fn pubkey_bytes(&self) -> BridgeAuthorityPublicKeyBytes {
63 BridgeAuthorityPublicKeyBytes::from(&self.pubkey)
64 }
65}
66
67#[derive(Debug, Clone)]
68pub struct BridgeCommittee {
69 members: BTreeMap<BridgeAuthorityPublicKeyBytes, BridgeAuthority>,
70 total_blocklisted_stake: StakeUnit,
71}
72
73impl BridgeCommittee {
74 pub fn new(members: Vec<BridgeAuthority>) -> BridgeResult<Self> {
75 let mut members_map = BTreeMap::new();
76 let mut total_blocklisted_stake = 0;
77 let mut total_stake = 0;
78 for member in members {
79 let public_key = BridgeAuthorityPublicKeyBytes::from(&member.pubkey);
80 if members_map.contains_key(&public_key) {
81 return Err(BridgeError::InvalidBridgeCommittee(
82 "Duplicate BridgeAuthority Public key".into(),
83 ));
84 }
85 if member.is_blocklisted {
87 total_blocklisted_stake += member.voting_power;
88 }
89 total_stake += member.voting_power;
90 members_map.insert(public_key, member);
91 }
92 if total_stake < BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER {
93 return Err(BridgeError::InvalidBridgeCommittee(format!(
94 "Total voting power is below minimal {BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER}"
95 )));
96 }
97 if total_stake > BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER {
98 return Err(BridgeError::InvalidBridgeCommittee(format!(
99 "Total voting power is above maximal {BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER}"
100 )));
101 }
102 Ok(Self {
103 members: members_map,
104 total_blocklisted_stake,
105 })
106 }
107
108 pub fn is_active_member(&self, member: &BridgeAuthorityPublicKeyBytes) -> bool {
109 self.members.contains_key(member) && !self.members.get(member).unwrap().is_blocklisted
110 }
111
112 pub fn members(&self) -> &BTreeMap<BridgeAuthorityPublicKeyBytes, BridgeAuthority> {
113 &self.members
114 }
115
116 pub fn member(&self, member: &BridgeAuthorityPublicKeyBytes) -> Option<&BridgeAuthority> {
117 self.members.get(member)
118 }
119
120 pub fn total_blocklisted_stake(&self) -> StakeUnit {
121 self.total_blocklisted_stake
122 }
123
124 pub fn active_stake(&self, member: &BridgeAuthorityPublicKeyBytes) -> StakeUnit {
125 self.members
126 .get(member)
127 .map(|a| if a.is_blocklisted { 0 } else { a.voting_power })
128 .unwrap_or(0)
129 }
130}
131
132impl core::fmt::Display for BridgeCommittee {
133 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
134 for m in self.members.values() {
135 writeln!(
136 f,
137 "pubkey: {:?}, url: {:?}, stake: {:?}, blocklisted: {}, eth address: {:x}",
138 Hex::encode(m.pubkey_bytes().as_bytes()),
139 m.base_url,
140 m.voting_power,
141 m.is_blocklisted,
142 m.pubkey_bytes().to_eth_address(),
143 )?;
144 }
145 Ok(())
146 }
147}
148
149impl CommitteeTrait<BridgeAuthorityPublicKeyBytes> for BridgeCommittee {
150 fn shuffle_by_stake_with_rng(
152 &self,
153 preferences: Option<&BTreeSet<BridgeAuthorityPublicKeyBytes>>,
158 restrict_to: Option<&BTreeSet<BridgeAuthorityPublicKeyBytes>>,
160 rng: &mut impl Rng,
161 ) -> Vec<BridgeAuthorityPublicKeyBytes> {
162 let mut candidates = self
163 .members
164 .iter()
165 .filter_map(|(name, a)| {
166 if a.is_blocklisted {
168 return None;
169 }
170 if let Some(restrict_to) = restrict_to {
172 match restrict_to.contains(name) {
173 true => Some((name.clone(), a.voting_power)),
174 false => None,
175 }
176 } else {
177 Some((name.clone(), a.voting_power))
178 }
179 })
180 .collect::<Vec<_>>();
181 if preferences.is_some() {
182 candidates.sort_by(|(_, a), (_, b)| b.cmp(a));
183 candidates.iter().map(|(name, _)| name.clone()).collect()
184 } else {
185 candidates
186 .choose_multiple_weighted(rng, candidates.len(), |(_, weight)| *weight as f64)
187 .unwrap()
189 .map(|(name, _)| name)
190 .cloned()
191 .collect()
192 }
193 }
194
195 fn weight(&self, author: &BridgeAuthorityPublicKeyBytes) -> StakeUnit {
196 self.members
197 .get(author)
198 .map(|a| a.voting_power)
199 .unwrap_or(0)
200 }
201}
202
203#[derive(Serialize, Copy, Clone, PartialEq, Eq, TryFromPrimitive, Hash, Display)]
204#[repr(u8)]
205pub enum BridgeActionType {
206 TokenTransfer = 0,
207 UpdateCommitteeBlocklist = 1,
208 EmergencyButton = 2,
209 LimitUpdate = 3,
210 AssetPriceUpdate = 4,
211 EvmContractUpgrade = 5,
212 AddTokensOnSui = 6,
213 AddTokensOnEvm = 7,
214}
215
216#[derive(Clone, PartialEq, Eq)]
217pub struct BridgeActionKey {
218 pub chain_id: BridgeChainId,
219 pub action_type: BridgeActionType,
220 pub seq_num: u64,
221}
222
223impl Debug for BridgeActionKey {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(
226 f,
227 "BridgeActionKey({},{},{})",
228 self.action_type as u8, self.chain_id as u8, self.seq_num
229 )
230 }
231}
232
233#[derive(Debug, PartialEq, Eq, Clone, TryFromPrimitive)]
234#[repr(u8)]
235pub enum BridgeActionStatus {
236 Pending = 0,
237 Approved = 1,
238 Claimed = 2,
239 NotFound = 3,
240}
241
242#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
243pub struct SuiToEthBridgeAction {
244 pub sui_tx_digest: TransactionDigest,
246 pub sui_tx_event_index: u16,
248 pub sui_bridge_event: EmittedSuiToEthTokenBridgeV1,
249}
250
251#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
252pub struct SuiToEthTokenTransfer {
253 pub nonce: u64,
254 pub sui_chain_id: BridgeChainId,
255 pub eth_chain_id: BridgeChainId,
256 pub sui_address: SuiAddress,
257 pub eth_address: EthAddress,
258 pub token_id: u8,
259 pub amount_adjusted: u64,
261}
262
263#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
264pub struct SuiToEthTokenTransferV2 {
265 pub nonce: u64,
266 pub sui_chain_id: BridgeChainId,
267 pub eth_chain_id: BridgeChainId,
268 pub sui_address: SuiAddress,
269 pub eth_address: EthAddress,
270 pub token_id: u8,
271 pub amount_adjusted: u64,
273 pub timestamp_ms: u64,
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
277pub struct EthToSuiBridgeAction {
278 pub eth_tx_hash: EthTransactionHash,
280 pub eth_event_index: u16,
282 pub eth_bridge_event: EthToSuiTokenBridgeV1,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
286pub struct EthToSuiTokenTransferV2 {
287 pub eth_tx_hash: EthTransactionHash,
289 pub eth_event_index: u16,
291 pub eth_bridge_event: EthToSuiTokenBridgeV2,
292}
293
294#[derive(
295 Debug,
296 Serialize,
297 Deserialize,
298 PartialEq,
299 Eq,
300 Clone,
301 Copy,
302 TryFromPrimitive,
303 Hash,
304 clap::ValueEnum,
305)]
306#[repr(u8)]
307pub enum BlocklistType {
308 Blocklist = 0,
309 Unblocklist = 1,
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
313pub struct BlocklistCommitteeAction {
314 pub nonce: u64,
315 pub chain_id: BridgeChainId,
316 pub blocklist_type: BlocklistType,
317 pub members_to_update: Vec<BridgeAuthorityPublicKeyBytes>,
318}
319
320#[derive(
321 Debug,
322 Serialize,
323 Deserialize,
324 PartialEq,
325 Eq,
326 Clone,
327 Copy,
328 TryFromPrimitive,
329 Hash,
330 clap::ValueEnum,
331)]
332#[repr(u8)]
333pub enum EmergencyActionType {
334 Pause = 0,
335 Unpause = 1,
336}
337
338#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
339pub struct EmergencyAction {
340 pub nonce: u64,
341 pub chain_id: BridgeChainId,
342 pub action_type: EmergencyActionType,
343}
344
345#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
346pub struct LimitUpdateAction {
347 pub nonce: u64,
348 pub chain_id: BridgeChainId,
352 pub sending_chain_id: BridgeChainId,
354 pub new_usd_limit: u64,
356}
357
358#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
359pub struct AssetPriceUpdateAction {
360 pub nonce: u64,
361 pub chain_id: BridgeChainId,
362 pub token_id: u8,
363 pub new_usd_price: u64,
364}
365
366#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
367pub struct EvmContractUpgradeAction {
368 pub nonce: u64,
369 pub chain_id: BridgeChainId,
370 pub proxy_address: EthAddress,
371 pub new_impl_address: EthAddress,
372 pub call_data: Vec<u8>,
373}
374
375#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
376pub struct AddTokensOnSuiAction {
377 pub nonce: u64,
378 pub chain_id: BridgeChainId,
379 pub native: bool,
380 pub token_ids: Vec<u8>,
381 pub token_type_names: Vec<TypeTag>,
382 pub token_prices: Vec<u64>,
383}
384
385#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
386pub struct AddTokensOnEvmAction {
387 pub nonce: u64,
388 pub chain_id: BridgeChainId,
389 pub native: bool,
390 pub token_ids: Vec<u8>,
391 pub token_addresses: Vec<EthAddress>,
392 pub token_sui_decimals: Vec<u8>,
393 pub token_prices: Vec<u64>,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
399#[enum_dispatch(BridgeMessageEncoding)]
400pub enum BridgeAction {
401 SuiToEthBridgeAction(SuiToEthBridgeAction),
403 EthToSuiBridgeAction(EthToSuiBridgeAction),
405 BlocklistCommitteeAction(BlocklistCommitteeAction),
406 EmergencyAction(EmergencyAction),
407 LimitUpdateAction(LimitUpdateAction),
408 AssetPriceUpdateAction(AssetPriceUpdateAction),
409 EvmContractUpgradeAction(EvmContractUpgradeAction),
410 AddTokensOnSuiAction(AddTokensOnSuiAction),
411 AddTokensOnEvmAction(AddTokensOnEvmAction),
412 SuiToEthTokenTransfer(SuiToEthTokenTransfer),
414 SuiToEthTokenTransferV2(SuiToEthTokenTransferV2),
416 EthToSuiTokenTransferV2(EthToSuiTokenTransferV2),
418}
419
420impl BridgeAction {
421 pub fn digest(&self) -> BridgeActionDigest {
423 let mut hasher = Keccak256::default();
424 hasher.update(
425 self.to_bytes()
426 .expect("Message encoding should not fail for valid actions"),
427 );
428 BridgeActionDigest::new(hasher.finalize().into())
429 }
430
431 pub fn key(&self) -> BridgeActionKey {
432 BridgeActionKey {
433 action_type: self.action_type(),
434 chain_id: self.chain_id(),
435 seq_num: self.seq_number(),
436 }
437 }
438
439 pub fn chain_id(&self) -> BridgeChainId {
440 match self {
441 BridgeAction::SuiToEthBridgeAction(a) => a.sui_bridge_event.sui_chain_id,
442 BridgeAction::SuiToEthTokenTransfer(a) => a.sui_chain_id,
443 BridgeAction::SuiToEthTokenTransferV2(a) => a.sui_chain_id,
444 BridgeAction::EthToSuiBridgeAction(a) => a.eth_bridge_event.eth_chain_id,
445 BridgeAction::EthToSuiTokenTransferV2(a) => a.eth_bridge_event.eth_chain_id,
446 BridgeAction::BlocklistCommitteeAction(a) => a.chain_id,
447 BridgeAction::EmergencyAction(a) => a.chain_id,
448 BridgeAction::LimitUpdateAction(a) => a.chain_id,
449 BridgeAction::AssetPriceUpdateAction(a) => a.chain_id,
450 BridgeAction::EvmContractUpgradeAction(a) => a.chain_id,
451 BridgeAction::AddTokensOnSuiAction(a) => a.chain_id,
452 BridgeAction::AddTokensOnEvmAction(a) => a.chain_id,
453 }
454 }
455
456 pub fn is_governance_action(&self) -> bool {
457 match self.action_type() {
458 BridgeActionType::TokenTransfer => false,
459 BridgeActionType::UpdateCommitteeBlocklist => true,
460 BridgeActionType::EmergencyButton => true,
461 BridgeActionType::LimitUpdate => true,
462 BridgeActionType::AssetPriceUpdate => true,
463 BridgeActionType::EvmContractUpgrade => true,
464 BridgeActionType::AddTokensOnSui => true,
465 BridgeActionType::AddTokensOnEvm => true,
466 }
467 }
468
469 pub fn action_type(&self) -> BridgeActionType {
471 match self {
472 BridgeAction::SuiToEthBridgeAction(_) => BridgeActionType::TokenTransfer,
473 BridgeAction::SuiToEthTokenTransfer(_) => BridgeActionType::TokenTransfer,
474 BridgeAction::SuiToEthTokenTransferV2(_) => BridgeActionType::TokenTransfer,
475 BridgeAction::EthToSuiBridgeAction(_) => BridgeActionType::TokenTransfer,
476 BridgeAction::EthToSuiTokenTransferV2(_) => BridgeActionType::TokenTransfer,
477 BridgeAction::BlocklistCommitteeAction(_) => BridgeActionType::UpdateCommitteeBlocklist,
478 BridgeAction::EmergencyAction(_) => BridgeActionType::EmergencyButton,
479 BridgeAction::LimitUpdateAction(_) => BridgeActionType::LimitUpdate,
480 BridgeAction::AssetPriceUpdateAction(_) => BridgeActionType::AssetPriceUpdate,
481 BridgeAction::EvmContractUpgradeAction(_) => BridgeActionType::EvmContractUpgrade,
482 BridgeAction::AddTokensOnSuiAction(_) => BridgeActionType::AddTokensOnSui,
483 BridgeAction::AddTokensOnEvmAction(_) => BridgeActionType::AddTokensOnEvm,
484 }
485 }
486
487 pub fn seq_number(&self) -> u64 {
489 match self {
490 BridgeAction::SuiToEthBridgeAction(a) => a.sui_bridge_event.nonce,
491 BridgeAction::SuiToEthTokenTransfer(a) => a.nonce,
492 BridgeAction::SuiToEthTokenTransferV2(a) => a.nonce,
493 BridgeAction::EthToSuiBridgeAction(a) => a.eth_bridge_event.nonce,
494 BridgeAction::EthToSuiTokenTransferV2(a) => a.eth_bridge_event.nonce,
495 BridgeAction::BlocklistCommitteeAction(a) => a.nonce,
496 BridgeAction::EmergencyAction(a) => a.nonce,
497 BridgeAction::LimitUpdateAction(a) => a.nonce,
498 BridgeAction::AssetPriceUpdateAction(a) => a.nonce,
499 BridgeAction::EvmContractUpgradeAction(a) => a.nonce,
500 BridgeAction::AddTokensOnSuiAction(a) => a.nonce,
501 BridgeAction::AddTokensOnEvmAction(a) => a.nonce,
502 }
503 }
504
505 pub fn approval_threshold(&self) -> u64 {
506 match self {
507 BridgeAction::SuiToEthBridgeAction(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
508 BridgeAction::SuiToEthTokenTransfer(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
509 BridgeAction::SuiToEthTokenTransferV2(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
510 BridgeAction::EthToSuiBridgeAction(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
511 BridgeAction::EthToSuiTokenTransferV2(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
512 BridgeAction::BlocklistCommitteeAction(_) => APPROVAL_THRESHOLD_COMMITTEE_BLOCKLIST,
513 BridgeAction::EmergencyAction(a) => match a.action_type {
514 EmergencyActionType::Pause => APPROVAL_THRESHOLD_EMERGENCY_PAUSE,
515 EmergencyActionType::Unpause => APPROVAL_THRESHOLD_EMERGENCY_UNPAUSE,
516 },
517 BridgeAction::LimitUpdateAction(_) => APPROVAL_THRESHOLD_LIMIT_UPDATE,
518 BridgeAction::AssetPriceUpdateAction(_) => APPROVAL_THRESHOLD_ASSET_PRICE_UPDATE,
519 BridgeAction::EvmContractUpgradeAction(_) => APPROVAL_THRESHOLD_EVM_CONTRACT_UPGRADE,
520 BridgeAction::AddTokensOnSuiAction(_) => APPROVAL_THRESHOLD_ADD_TOKENS_ON_SUI,
521 BridgeAction::AddTokensOnEvmAction(_) => APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM,
522 }
523 }
524
525 pub fn update_to_token_transfer(self) -> Self {
527 match self {
528 BridgeAction::SuiToEthBridgeAction(a) => {
529 BridgeAction::SuiToEthTokenTransfer(SuiToEthTokenTransfer {
530 nonce: a.sui_bridge_event.nonce,
531 sui_chain_id: a.sui_bridge_event.sui_chain_id,
532 eth_chain_id: a.sui_bridge_event.eth_chain_id,
533 sui_address: a.sui_bridge_event.sui_address,
534 eth_address: a.sui_bridge_event.eth_address,
535 token_id: a.sui_bridge_event.token_id,
536 amount_adjusted: a.sui_bridge_event.amount_sui_adjusted,
537 })
538 }
539 BridgeAction::EthToSuiBridgeAction(_) => self,
540 BridgeAction::EthToSuiTokenTransferV2(_) => self,
541 BridgeAction::BlocklistCommitteeAction(_) => self,
542 BridgeAction::EmergencyAction(_) => self,
543 BridgeAction::LimitUpdateAction(_) => self,
544 BridgeAction::AssetPriceUpdateAction(_) => self,
545 BridgeAction::EvmContractUpgradeAction(_) => self,
546 BridgeAction::AddTokensOnSuiAction(_) => self,
547 BridgeAction::AddTokensOnEvmAction(_) => self,
548 BridgeAction::SuiToEthTokenTransfer(_) => self,
549 BridgeAction::SuiToEthTokenTransferV2(_) => self,
550 }
551 }
552
553 pub fn try_from_bridge_record(record: &MoveTypeBridgeRecord) -> BridgeResult<Self> {
554 use std::str::FromStr;
555
556 let MoveTypeBridgeMessage {
557 message_type: _,
558 message_version,
559 seq_num,
560 source_chain,
561 payload,
562 } = &record.message;
563
564 #[derive(Debug, Deserialize)]
565 struct SuiToEthOnChainBcsPayload {
566 sui_address: Vec<u8>,
567 target_chain: u8,
568 eth_address: Vec<u8>,
569 token_type: u8,
570 amount: [u8; 8], }
572
573 #[derive(Debug, Deserialize)]
574 struct SuiToEthOnChainBcsPayloadV2 {
575 sui_address: Vec<u8>,
576 target_chain: u8,
577 eth_address: Vec<u8>,
578 token_type: u8,
579 amount: [u8; 8], timestamp_ms: [u8; 8], }
582
583 match *message_version {
584 crate::encoding::TOKEN_TRANSFER_MESSAGE_VERSION_V1 => {
585 let payload: SuiToEthOnChainBcsPayload = bcs::from_bytes(payload)?;
586
587 Ok(BridgeAction::SuiToEthTokenTransfer(SuiToEthTokenTransfer {
588 nonce: *seq_num,
589 sui_chain_id: BridgeChainId::try_from(*source_chain)?,
590 eth_chain_id: BridgeChainId::try_from(payload.target_chain)?,
591 sui_address: SuiAddress::from_bytes(payload.sui_address)?,
592 eth_address: EthAddress::from_str(&Hex::encode(&payload.eth_address))?,
593 token_id: payload.token_type,
594 amount_adjusted: u64::from_be_bytes(payload.amount),
595 }))
596 }
597 crate::encoding::TOKEN_TRANSFER_MESSAGE_VERSION_V2 => {
598 let payload: SuiToEthOnChainBcsPayloadV2 = bcs::from_bytes(payload)?;
599
600 Ok(BridgeAction::SuiToEthTokenTransferV2(
601 SuiToEthTokenTransferV2 {
602 nonce: *seq_num,
603 sui_chain_id: BridgeChainId::try_from(*source_chain)?,
604 eth_chain_id: BridgeChainId::try_from(payload.target_chain)?,
605 sui_address: SuiAddress::from_bytes(payload.sui_address)?,
606 eth_address: EthAddress::from_str(&Hex::encode(&payload.eth_address))?,
607 token_id: payload.token_type,
608 amount_adjusted: u64::from_be_bytes(payload.amount),
609 timestamp_ms: u64::from_be_bytes(payload.timestamp_ms),
610 },
611 ))
612 }
613 v => Err(BridgeError::Generic(format!(
614 "unknown message version: {v}"
615 ))),
616 }
617 }
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
621pub struct BridgeActionDigest(Digest);
622
623impl BridgeActionDigest {
624 pub const fn new(digest: [u8; 32]) -> Self {
625 Self(Digest::new(digest))
626 }
627}
628
629#[derive(Debug, Clone)]
630pub struct BridgeCommitteeValiditySignInfo {
631 pub signatures: BTreeMap<BridgeAuthorityPublicKeyBytes, BridgeAuthorityRecoverableSignature>,
632}
633
634pub type SignedBridgeAction = Envelope<BridgeAction, BridgeAuthoritySignInfo>;
635pub type VerifiedSignedBridgeAction = VerifiedEnvelope<BridgeAction, BridgeAuthoritySignInfo>;
636pub type CertifiedBridgeAction = Envelope<BridgeAction, BridgeCommitteeValiditySignInfo>;
637pub type VerifiedCertifiedBridgeAction =
638 VerifiedEnvelope<BridgeAction, BridgeCommitteeValiditySignInfo>;
639
640#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
641pub struct BridgeEventDigest(Digest);
642
643impl BridgeEventDigest {
644 pub const fn new(digest: [u8; 32]) -> Self {
645 Self(Digest::new(digest))
646 }
647}
648
649impl Message for BridgeAction {
650 type DigestType = BridgeEventDigest;
651
652 const SCOPE: IntentScope = IntentScope::BridgeEventUnused;
654
655 fn digest(&self) -> Self::DigestType {
657 unreachable!("BridgeEventDigest is not used today")
658 }
659}
660
661#[derive(Debug, Clone, PartialEq, Eq)]
662pub struct EthLog {
663 pub block_number: u64,
664 pub tx_hash: B256,
665 pub log_index_in_tx: u16,
666 pub log: Log,
667}
668
669#[derive(Debug, Clone, PartialEq, Eq)]
672pub struct RawEthLog {
673 pub block_number: u64,
674 pub tx_hash: B256,
675 pub log: Log,
676}
677
678pub trait EthEvent {
679 fn block_number(&self) -> u64;
680 fn tx_hash(&self) -> B256;
681 fn log(&self) -> &Log;
682}
683
684impl EthEvent for EthLog {
685 fn block_number(&self) -> u64 {
686 self.block_number
687 }
688 fn tx_hash(&self) -> B256 {
689 self.tx_hash
690 }
691 fn log(&self) -> &Log {
692 &self.log
693 }
694}
695
696impl EthEvent for RawEthLog {
697 fn block_number(&self) -> u64 {
698 self.block_number
699 }
700 fn tx_hash(&self) -> B256 {
701 self.tx_hash
702 }
703 fn log(&self) -> &Log {
704 &self.log
705 }
706}
707
708pub fn is_route_valid(one: BridgeChainId, other: BridgeChainId) -> bool {
711 if one.is_sui_chain() && other.is_sui_chain() {
712 return false;
713 }
714 if !one.is_sui_chain() && !other.is_sui_chain() {
715 return false;
716 }
717 if one == BridgeChainId::EthMainnet {
718 return other == BridgeChainId::SuiMainnet;
719 }
720 if one == BridgeChainId::SuiMainnet {
721 return other == BridgeChainId::EthMainnet;
722 }
723 if other == BridgeChainId::EthMainnet {
724 return one == BridgeChainId::SuiMainnet;
725 }
726 if other == BridgeChainId::SuiMainnet {
727 return one == BridgeChainId::EthMainnet;
728 }
729 true
730}
731
732#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
734pub struct ParsedTokenTransferMessage {
735 pub message_version: u8,
736 pub seq_num: u64,
737 pub source_chain: BridgeChainId,
738 pub payload: Vec<u8>,
739 pub parsed_payload: MoveTypeTokenTransferPayload,
740}
741
742impl TryFrom<MoveTypeParsedTokenTransferMessage> for ParsedTokenTransferMessage {
743 type Error = BridgeError;
744
745 fn try_from(message: MoveTypeParsedTokenTransferMessage) -> BridgeResult<Self> {
746 let source_chain = BridgeChainId::try_from(message.source_chain).map_err(|_e| {
747 BridgeError::Generic(format!(
748 "Failed to convert MoveTypeParsedTokenTransferMessage to ParsedTokenTransferMessage. Failed to convert source chain {} to BridgeChainId",
749 message.source_chain,
750 ))
751 })?;
752 Ok(Self {
753 message_version: message.message_version,
754 seq_num: message.seq_num,
755 source_chain,
756 payload: message.payload,
757 parsed_payload: message.parsed_payload,
758 })
759 }
760}
761
762pub struct SuiEvents {
763 pub transaction_digest: TransactionDigest,
764 pub checkpoint: Option<u64>,
765 pub timestamp_ms: Option<u64>,
766 pub events: Vec<sui_json_rpc_types::SuiEvent>,
767}
768
769#[cfg(test)]
770mod tests {
771 use crate::test_utils::get_test_authority_and_key;
772 use crate::test_utils::get_test_eth_to_sui_bridge_action;
773 use crate::test_utils::get_test_sui_to_eth_bridge_action;
774 use alloy::primitives::Address as EthAddress;
775 use fastcrypto::traits::KeyPair;
776 use std::collections::HashSet;
777 use sui_types::bridge::TOKEN_ID_BTC;
778 use sui_types::crypto::get_key_pair;
779
780 use super::*;
781
782 #[test]
783 fn test_bridge_committee_construction() -> anyhow::Result<()> {
784 let (mut authority, _, _) = get_test_authority_and_key(8000, 9999);
785 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap();
787
788 authority.voting_power = BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER - 1;
790 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap_err();
791
792 authority.voting_power = BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER + 1;
794 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap_err();
795
796 authority.voting_power = 5000;
798 let mut authority_2 = authority.clone();
799 let (_, kp): (_, fastcrypto::secp256k1::Secp256k1KeyPair) = get_key_pair();
800 let pubkey = kp.public().clone();
801 authority_2.pubkey = pubkey.clone();
802 let _ = BridgeCommittee::new(vec![authority.clone(), authority_2.clone()]).unwrap();
803
804 authority_2.pubkey = authority.pubkey.clone();
806 let _ = BridgeCommittee::new(vec![authority.clone(), authority.clone()]).unwrap_err();
807 Ok(())
808 }
809
810 #[test]
811 fn test_bridge_committee_total_blocklisted_stake() -> anyhow::Result<()> {
812 let (mut authority1, _, _) = get_test_authority_and_key(10000, 9999);
813 assert_eq!(
814 BridgeCommittee::new(vec![authority1.clone()])
815 .unwrap()
816 .total_blocklisted_stake(),
817 0
818 );
819 authority1.voting_power = 6000;
820
821 let (mut authority2, _, _) = get_test_authority_and_key(4000, 9999);
822 authority2.is_blocklisted = true;
823 assert_eq!(
824 BridgeCommittee::new(vec![authority1.clone(), authority2.clone()])
825 .unwrap()
826 .total_blocklisted_stake(),
827 4000
828 );
829
830 authority1.voting_power = 7000;
831 authority2.voting_power = 2000;
832 let (mut authority3, _, _) = get_test_authority_and_key(1000, 9999);
833 authority3.is_blocklisted = true;
834 assert_eq!(
835 BridgeCommittee::new(vec![authority1, authority2, authority3])
836 .unwrap()
837 .total_blocklisted_stake(),
838 3000
839 );
840
841 Ok(())
842 }
843
844 #[test]
846 fn test_bridge_action_approval_threshold_regression_test() -> anyhow::Result<()> {
847 let action = get_test_sui_to_eth_bridge_action(None, None, None, None, None, None, None);
848 assert_eq!(action.approval_threshold(), 3334);
849
850 let action = get_test_eth_to_sui_bridge_action(None, None, None, None);
851 assert_eq!(action.approval_threshold(), 3334);
852
853 let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
854 nonce: 94,
855 chain_id: BridgeChainId::EthSepolia,
856 blocklist_type: BlocklistType::Unblocklist,
857 members_to_update: vec![],
858 });
859 assert_eq!(action.approval_threshold(), 5001);
860
861 let action = BridgeAction::EmergencyAction(EmergencyAction {
862 nonce: 56,
863 chain_id: BridgeChainId::EthSepolia,
864 action_type: EmergencyActionType::Pause,
865 });
866 assert_eq!(action.approval_threshold(), 450);
867
868 let action = BridgeAction::EmergencyAction(EmergencyAction {
869 nonce: 56,
870 chain_id: BridgeChainId::EthSepolia,
871 action_type: EmergencyActionType::Unpause,
872 });
873 assert_eq!(action.approval_threshold(), 5001);
874
875 let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
876 nonce: 15,
877 chain_id: BridgeChainId::SuiCustom,
878 sending_chain_id: BridgeChainId::EthCustom,
879 new_usd_limit: 1_000_000 * USD_MULTIPLIER,
880 });
881 assert_eq!(action.approval_threshold(), 5001);
882
883 let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
884 nonce: 266,
885 chain_id: BridgeChainId::SuiCustom,
886 token_id: TOKEN_ID_BTC,
887 new_usd_price: 100_000 * USD_MULTIPLIER,
888 });
889 assert_eq!(action.approval_threshold(), 5001);
890
891 let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
892 nonce: 123,
893 chain_id: BridgeChainId::EthCustom,
894 proxy_address: EthAddress::repeat_byte(6),
895 new_impl_address: EthAddress::repeat_byte(9),
896 call_data: vec![],
897 });
898 assert_eq!(action.approval_threshold(), 5001);
899 Ok(())
900 }
901
902 #[test]
903 fn test_bridge_committee_filter_blocklisted_authorities() -> anyhow::Result<()> {
904 let (authority1, _, _) = get_test_authority_and_key(5000, 9999);
906 let (mut authority2, _, _) = get_test_authority_and_key(3000, 9999);
907 authority2.is_blocklisted = true;
908 let (authority3, _, _) = get_test_authority_and_key(2000, 9999);
909 let committee = BridgeCommittee::new(vec![
910 authority1.clone(),
911 authority2.clone(),
912 authority3.clone(),
913 ])
914 .unwrap();
915
916 let result = committee
918 .shuffle_by_stake(None, None)
919 .into_iter()
920 .collect::<HashSet<_>>();
921 assert_eq!(
922 HashSet::from_iter(vec![authority1.pubkey_bytes(), authority3.pubkey_bytes()]),
923 result
924 );
925
926 let result = committee
928 .shuffle_by_stake(
929 None,
930 Some(
931 &[authority1.pubkey_bytes(), authority2.pubkey_bytes()]
932 .iter()
933 .cloned()
934 .collect(),
935 ),
936 )
937 .into_iter()
938 .collect::<HashSet<_>>();
939 assert_eq!(HashSet::from_iter(vec![authority1.pubkey_bytes()]), result);
940
941 Ok(())
942 }
943}