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