1use std::{
6 collections::{BTreeMap, BTreeSet},
7 fmt::Debug,
8};
9
10use enum_dispatch::enum_dispatch;
11pub use ethers::types::H256 as EthTransactionHash;
12use ethers::types::{Address as EthAddress, H256, Log};
13use fastcrypto::hash::{HashFunction, Keccak256};
14use iota_types::{
15 TypeTag,
16 bridge::{
17 APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM, APPROVAL_THRESHOLD_ADD_TOKENS_ON_IOTA,
18 APPROVAL_THRESHOLD_ASSET_PRICE_UPDATE, APPROVAL_THRESHOLD_COMMITTEE_BLOCKLIST,
19 APPROVAL_THRESHOLD_EMERGENCY_PAUSE, APPROVAL_THRESHOLD_EMERGENCY_UNPAUSE,
20 APPROVAL_THRESHOLD_EVM_CONTRACT_UPGRADE, APPROVAL_THRESHOLD_LIMIT_UPDATE,
21 APPROVAL_THRESHOLD_TOKEN_TRANSFER, BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER,
22 BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER, BridgeChainId, MoveTypeParsedTokenTransferMessage,
23 MoveTypeTokenTransferPayload,
24 },
25 committee::{CommitteeTrait, StakeUnit},
26 digests::{Digest, TransactionDigest},
27 message_envelope::{Envelope, Message, VerifiedEnvelope},
28};
29use num_enum::TryFromPrimitive;
30use rand::{Rng, seq::SliceRandom};
31use serde::{Deserialize, Serialize};
32use shared_crypto::intent::IntentScope;
33
34use crate::{
35 abi::EthToIotaTokenBridgeV1,
36 crypto::{
37 BridgeAuthorityPublicKey, BridgeAuthorityPublicKeyBytes,
38 BridgeAuthorityRecoverableSignature, BridgeAuthoritySignInfo,
39 },
40 encoding::BridgeMessageEncoding,
41 error::{BridgeError, BridgeResult},
42 events::EmittedIotaToEthTokenBridgeV1,
43};
44
45pub const BRIDGE_AUTHORITY_TOTAL_VOTING_POWER: u64 = 10000;
46
47pub const USD_MULTIPLIER: u64 = 10000; pub type IsBridgePaused = bool;
50pub const BRIDGE_PAUSED: bool = true;
51pub const BRIDGE_UNPAUSED: bool = false;
52
53#[derive(Debug, Eq, PartialEq, Clone)]
54pub struct BridgeAuthority {
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
125impl CommitteeTrait<BridgeAuthorityPublicKeyBytes> for BridgeCommittee {
126 fn shuffle_by_stake_with_rng(
128 &self,
129 preferences: Option<&BTreeSet<BridgeAuthorityPublicKeyBytes>>,
135 restrict_to: Option<&BTreeSet<BridgeAuthorityPublicKeyBytes>>,
137 rng: &mut impl Rng,
138 ) -> Vec<BridgeAuthorityPublicKeyBytes> {
139 let mut candidates = self
140 .members
141 .iter()
142 .filter_map(|(name, a)| {
143 if a.is_blocklisted {
145 return None;
146 }
147 if let Some(restrict_to) = restrict_to {
149 match restrict_to.contains(name) {
150 true => Some((name.clone(), a.voting_power)),
151 false => None,
152 }
153 } else {
154 Some((name.clone(), a.voting_power))
155 }
156 })
157 .collect::<Vec<_>>();
158 if preferences.is_some() {
159 candidates.sort_by(|(_, a), (_, b)| b.cmp(a));
160 candidates.iter().map(|(name, _)| name.clone()).collect()
161 } else {
162 candidates
163 .choose_multiple_weighted(rng, candidates.len(), |(_, weight)| *weight as f64)
164 .unwrap()
167 .map(|(name, _)| name)
168 .cloned()
169 .collect()
170 }
171 }
172
173 fn weight(&self, author: &BridgeAuthorityPublicKeyBytes) -> StakeUnit {
174 self.members
175 .get(author)
176 .map(|a| a.voting_power)
177 .unwrap_or(0)
178 }
179}
180
181#[derive(Serialize, Copy, Clone, PartialEq, Eq, TryFromPrimitive, Hash)]
182#[repr(u8)]
183pub enum BridgeActionType {
184 TokenTransfer = 0,
185 UpdateCommitteeBlocklist = 1,
186 EmergencyButton = 2,
187 LimitUpdate = 3,
188 AssetPriceUpdate = 4,
189 EvmContractUpgrade = 5,
190 AddTokensOnIota = 6,
191 AddTokensOnEvm = 7,
192}
193
194#[derive(Clone, PartialEq, Eq)]
195pub struct BridgeActionKey {
196 pub action_type: BridgeActionType,
197 pub chain_id: BridgeChainId,
198 pub seq_num: u64,
199}
200
201impl Debug for BridgeActionKey {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(
204 f,
205 "BridgeActionKey({},{},{})",
206 self.action_type as u8, self.chain_id as u8, self.seq_num
207 )
208 }
209}
210
211#[derive(Debug, PartialEq, Eq, Clone, TryFromPrimitive)]
212#[repr(u8)]
213pub enum BridgeActionStatus {
214 Pending = 0,
215 Approved = 1,
216 Claimed = 2,
217 NotFound = 3,
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
221pub struct IotaToEthBridgeAction {
222 pub iota_tx_digest: TransactionDigest,
224 pub iota_tx_event_index: u16,
226 pub iota_bridge_event: EmittedIotaToEthTokenBridgeV1,
227}
228
229#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
230pub struct EthToIotaBridgeAction {
231 pub eth_tx_hash: EthTransactionHash,
233 pub eth_event_index: u16,
235 pub eth_bridge_event: EthToIotaTokenBridgeV1,
236}
237
238#[derive(
239 Debug,
240 Serialize,
241 Deserialize,
242 PartialEq,
243 Eq,
244 Clone,
245 Copy,
246 TryFromPrimitive,
247 Hash,
248 clap::ValueEnum,
249)]
250#[repr(u8)]
251pub enum BlocklistType {
252 Blocklist = 0,
253 Unblocklist = 1,
254}
255
256#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
257pub struct BlocklistCommitteeAction {
258 pub nonce: u64,
259 pub chain_id: BridgeChainId,
260 pub blocklist_type: BlocklistType,
261 pub members_to_update: Vec<BridgeAuthorityPublicKeyBytes>,
262}
263
264#[derive(
265 Debug,
266 Serialize,
267 Deserialize,
268 PartialEq,
269 Eq,
270 Clone,
271 Copy,
272 TryFromPrimitive,
273 Hash,
274 clap::ValueEnum,
275)]
276#[repr(u8)]
277pub enum EmergencyActionType {
278 Pause = 0,
279 Unpause = 1,
280}
281
282#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
283pub struct EmergencyAction {
284 pub nonce: u64,
285 pub chain_id: BridgeChainId,
286 pub action_type: EmergencyActionType,
287}
288
289#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
290pub struct LimitUpdateAction {
291 pub nonce: u64,
292 pub chain_id: BridgeChainId,
297 pub sending_chain_id: BridgeChainId,
299 pub new_usd_limit: u64,
301}
302
303#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
304pub struct AssetPriceUpdateAction {
305 pub nonce: u64,
306 pub chain_id: BridgeChainId,
307 pub token_id: u8,
308 pub new_usd_price: u64,
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
312pub struct EvmContractUpgradeAction {
313 pub nonce: u64,
314 pub chain_id: BridgeChainId,
315 pub proxy_address: EthAddress,
316 pub new_impl_address: EthAddress,
317 pub call_data: Vec<u8>,
318}
319
320#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
321pub struct AddTokensOnIotaAction {
322 pub nonce: u64,
323 pub chain_id: BridgeChainId,
324 pub native: bool,
325 pub token_ids: Vec<u8>,
326 pub token_type_names: Vec<TypeTag>,
327 pub token_prices: Vec<u64>,
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
331pub struct AddTokensOnEvmAction {
332 pub nonce: u64,
333 pub chain_id: BridgeChainId,
334 pub native: bool,
335 pub token_ids: Vec<u8>,
336 pub token_addresses: Vec<EthAddress>,
337 pub token_iota_decimals: Vec<u8>,
338 pub token_prices: Vec<u64>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
344#[enum_dispatch(BridgeMessageEncoding)]
345pub enum BridgeAction {
346 IotaToEthBridgeAction(IotaToEthBridgeAction),
348 EthToIotaBridgeAction(EthToIotaBridgeAction),
350 BlocklistCommitteeAction(BlocklistCommitteeAction),
351 EmergencyAction(EmergencyAction),
352 LimitUpdateAction(LimitUpdateAction),
353 AssetPriceUpdateAction(AssetPriceUpdateAction),
354 EvmContractUpgradeAction(EvmContractUpgradeAction),
355 AddTokensOnIotaAction(AddTokensOnIotaAction),
356 AddTokensOnEvmAction(AddTokensOnEvmAction),
357}
358
359impl BridgeAction {
360 pub fn digest(&self) -> BridgeActionDigest {
362 let mut hasher = Keccak256::default();
363 hasher.update(self.to_bytes());
364 BridgeActionDigest::new(hasher.finalize().into())
365 }
366
367 pub fn key(&self) -> BridgeActionKey {
368 BridgeActionKey {
369 action_type: self.action_type(),
370 chain_id: self.chain_id(),
371 seq_num: self.seq_number(),
372 }
373 }
374
375 pub fn chain_id(&self) -> BridgeChainId {
376 match self {
377 BridgeAction::IotaToEthBridgeAction(a) => a.iota_bridge_event.iota_chain_id,
378 BridgeAction::EthToIotaBridgeAction(a) => a.eth_bridge_event.eth_chain_id,
379 BridgeAction::BlocklistCommitteeAction(a) => a.chain_id,
380 BridgeAction::EmergencyAction(a) => a.chain_id,
381 BridgeAction::LimitUpdateAction(a) => a.chain_id,
382 BridgeAction::AssetPriceUpdateAction(a) => a.chain_id,
383 BridgeAction::EvmContractUpgradeAction(a) => a.chain_id,
384 BridgeAction::AddTokensOnIotaAction(a) => a.chain_id,
385 BridgeAction::AddTokensOnEvmAction(a) => a.chain_id,
386 }
387 }
388
389 pub fn is_governace_action(&self) -> bool {
390 match self.action_type() {
391 BridgeActionType::TokenTransfer => false,
392 BridgeActionType::UpdateCommitteeBlocklist => true,
393 BridgeActionType::EmergencyButton => true,
394 BridgeActionType::LimitUpdate => true,
395 BridgeActionType::AssetPriceUpdate => true,
396 BridgeActionType::EvmContractUpgrade => true,
397 BridgeActionType::AddTokensOnIota => true,
398 BridgeActionType::AddTokensOnEvm => true,
399 }
400 }
401
402 pub fn action_type(&self) -> BridgeActionType {
404 match self {
405 BridgeAction::IotaToEthBridgeAction(_) => BridgeActionType::TokenTransfer,
406 BridgeAction::EthToIotaBridgeAction(_) => BridgeActionType::TokenTransfer,
407 BridgeAction::BlocklistCommitteeAction(_) => BridgeActionType::UpdateCommitteeBlocklist,
408 BridgeAction::EmergencyAction(_) => BridgeActionType::EmergencyButton,
409 BridgeAction::LimitUpdateAction(_) => BridgeActionType::LimitUpdate,
410 BridgeAction::AssetPriceUpdateAction(_) => BridgeActionType::AssetPriceUpdate,
411 BridgeAction::EvmContractUpgradeAction(_) => BridgeActionType::EvmContractUpgrade,
412 BridgeAction::AddTokensOnIotaAction(_) => BridgeActionType::AddTokensOnIota,
413 BridgeAction::AddTokensOnEvmAction(_) => BridgeActionType::AddTokensOnEvm,
414 }
415 }
416
417 pub fn seq_number(&self) -> u64 {
419 match self {
420 BridgeAction::IotaToEthBridgeAction(a) => a.iota_bridge_event.nonce,
421 BridgeAction::EthToIotaBridgeAction(a) => a.eth_bridge_event.nonce,
422 BridgeAction::BlocklistCommitteeAction(a) => a.nonce,
423 BridgeAction::EmergencyAction(a) => a.nonce,
424 BridgeAction::LimitUpdateAction(a) => a.nonce,
425 BridgeAction::AssetPriceUpdateAction(a) => a.nonce,
426 BridgeAction::EvmContractUpgradeAction(a) => a.nonce,
427 BridgeAction::AddTokensOnIotaAction(a) => a.nonce,
428 BridgeAction::AddTokensOnEvmAction(a) => a.nonce,
429 }
430 }
431
432 pub fn approval_threshold(&self) -> u64 {
433 match self {
434 BridgeAction::IotaToEthBridgeAction(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
435 BridgeAction::EthToIotaBridgeAction(_) => APPROVAL_THRESHOLD_TOKEN_TRANSFER,
436 BridgeAction::BlocklistCommitteeAction(_) => APPROVAL_THRESHOLD_COMMITTEE_BLOCKLIST,
437 BridgeAction::EmergencyAction(a) => match a.action_type {
438 EmergencyActionType::Pause => APPROVAL_THRESHOLD_EMERGENCY_PAUSE,
439 EmergencyActionType::Unpause => APPROVAL_THRESHOLD_EMERGENCY_UNPAUSE,
440 },
441 BridgeAction::LimitUpdateAction(_) => APPROVAL_THRESHOLD_LIMIT_UPDATE,
442 BridgeAction::AssetPriceUpdateAction(_) => APPROVAL_THRESHOLD_ASSET_PRICE_UPDATE,
443 BridgeAction::EvmContractUpgradeAction(_) => APPROVAL_THRESHOLD_EVM_CONTRACT_UPGRADE,
444 BridgeAction::AddTokensOnIotaAction(_) => APPROVAL_THRESHOLD_ADD_TOKENS_ON_IOTA,
445 BridgeAction::AddTokensOnEvmAction(_) => APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM,
446 }
447 }
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
451pub struct BridgeActionDigest(Digest);
452
453impl BridgeActionDigest {
454 pub const fn new(digest: [u8; 32]) -> Self {
455 Self(Digest::new(digest))
456 }
457}
458
459#[derive(Debug, Clone)]
460pub struct BridgeCommitteeValiditySignInfo {
461 pub signatures: BTreeMap<BridgeAuthorityPublicKeyBytes, BridgeAuthorityRecoverableSignature>,
462}
463
464pub type SignedBridgeAction = Envelope<BridgeAction, BridgeAuthoritySignInfo>;
465pub type VerifiedSignedBridgeAction = VerifiedEnvelope<BridgeAction, BridgeAuthoritySignInfo>;
466pub type CertifiedBridgeAction = Envelope<BridgeAction, BridgeCommitteeValiditySignInfo>;
467pub type VerifiedCertifiedBridgeAction =
468 VerifiedEnvelope<BridgeAction, BridgeCommitteeValiditySignInfo>;
469
470#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
471pub struct BridgeEventDigest(Digest);
472
473impl BridgeEventDigest {
474 pub const fn new(digest: [u8; 32]) -> Self {
475 Self(Digest::new(digest))
476 }
477}
478
479impl Message for BridgeAction {
480 type DigestType = BridgeEventDigest;
481
482 const SCOPE: IntentScope = IntentScope::BridgeEventUnused;
484
485 fn digest(&self) -> Self::DigestType {
487 unreachable!("BridgeEventDigest is not used today")
488 }
489}
490
491#[derive(Debug, Clone, PartialEq, Eq)]
492pub struct EthLog {
493 pub block_number: u64,
494 pub tx_hash: H256,
495 pub log_index_in_tx: u16,
496 pub log: Log,
497}
498
499#[derive(Debug, Clone, PartialEq, Eq)]
502pub struct RawEthLog {
503 pub block_number: u64,
504 pub tx_hash: H256,
505 pub log: Log,
506}
507
508pub trait EthEvent {
509 fn block_number(&self) -> u64;
510 fn tx_hash(&self) -> H256;
511 fn log(&self) -> &Log;
512}
513
514impl EthEvent for EthLog {
515 fn block_number(&self) -> u64 {
516 self.block_number
517 }
518 fn tx_hash(&self) -> H256 {
519 self.tx_hash
520 }
521 fn log(&self) -> &Log {
522 &self.log
523 }
524}
525
526impl EthEvent for RawEthLog {
527 fn block_number(&self) -> u64 {
528 self.block_number
529 }
530 fn tx_hash(&self) -> H256 {
531 self.tx_hash
532 }
533 fn log(&self) -> &Log {
534 &self.log
535 }
536}
537
538pub fn is_route_valid(one: BridgeChainId, other: BridgeChainId) -> bool {
541 if one.is_iota_chain() && other.is_iota_chain() {
542 return false;
543 }
544 if !one.is_iota_chain() && !other.is_iota_chain() {
545 return false;
546 }
547 if one == BridgeChainId::EthMainnet {
548 return other == BridgeChainId::IotaMainnet;
549 }
550 if one == BridgeChainId::IotaMainnet {
551 return other == BridgeChainId::EthMainnet;
552 }
553 if other == BridgeChainId::EthMainnet {
554 return one == BridgeChainId::IotaMainnet;
555 }
556 if other == BridgeChainId::IotaMainnet {
557 return one == BridgeChainId::EthMainnet;
558 }
559 true
560}
561
562#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
564pub struct ParsedTokenTransferMessage {
565 pub message_version: u8,
566 pub seq_num: u64,
567 pub source_chain: BridgeChainId,
568 pub payload: Vec<u8>,
569 pub parsed_payload: MoveTypeTokenTransferPayload,
570}
571
572impl TryFrom<MoveTypeParsedTokenTransferMessage> for ParsedTokenTransferMessage {
573 type Error = BridgeError;
574
575 fn try_from(message: MoveTypeParsedTokenTransferMessage) -> BridgeResult<Self> {
576 let source_chain = BridgeChainId::try_from(message.source_chain).map_err(|_e| {
577 BridgeError::Generic(format!(
578 "Failed to convert MoveTypeParsedTokenTransferMessage to ParsedTokenTransferMessage. Failed to convert source chain {} to BridgeChainId",
579 message.source_chain,
580 ))
581 })?;
582 Ok(Self {
583 message_version: message.message_version,
584 seq_num: message.seq_num,
585 source_chain,
586 payload: message.payload,
587 parsed_payload: message.parsed_payload,
588 })
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use std::collections::HashSet;
595
596 use ethers::types::Address as EthAddress;
597 use fastcrypto::traits::KeyPair;
598 use iota_types::{bridge::TOKEN_ID_BTC, crypto::get_key_pair};
599
600 use super::*;
601 use crate::test_utils::{
602 get_test_authority_and_key, get_test_eth_to_iota_bridge_action,
603 get_test_iota_to_eth_bridge_action,
604 };
605
606 #[test]
607 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
608 fn test_bridge_committee_construction() -> anyhow::Result<()> {
609 let (mut authority, _, _) = get_test_authority_and_key(8000, 9999);
610 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap();
612
613 authority.voting_power = BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER - 1;
615 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap_err();
616
617 authority.voting_power = BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER + 1;
619 let _ = BridgeCommittee::new(vec![authority.clone()]).unwrap_err();
620
621 authority.voting_power = 5000;
623 let mut authority_2 = authority.clone();
624 let (_, kp): (_, fastcrypto::secp256k1::Secp256k1KeyPair) = get_key_pair();
625 let pubkey = kp.public().clone();
626 authority_2.pubkey = pubkey.clone();
627 let _ = BridgeCommittee::new(vec![authority.clone(), authority_2.clone()]).unwrap();
628
629 authority_2.pubkey = authority.pubkey.clone();
631 let _ = BridgeCommittee::new(vec![authority.clone(), authority.clone()]).unwrap_err();
632 Ok(())
633 }
634
635 #[test]
636 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
637 fn test_bridge_committee_total_blocklisted_stake() -> anyhow::Result<()> {
638 let (mut authority1, _, _) = get_test_authority_and_key(10000, 9999);
639 assert_eq!(
640 BridgeCommittee::new(vec![authority1.clone()])
641 .unwrap()
642 .total_blocklisted_stake(),
643 0
644 );
645 authority1.voting_power = 6000;
646
647 let (mut authority2, _, _) = get_test_authority_and_key(4000, 9999);
648 authority2.is_blocklisted = true;
649 assert_eq!(
650 BridgeCommittee::new(vec![authority1.clone(), authority2.clone()])
651 .unwrap()
652 .total_blocklisted_stake(),
653 4000
654 );
655
656 authority1.voting_power = 7000;
657 authority2.voting_power = 2000;
658 let (mut authority3, _, _) = get_test_authority_and_key(1000, 9999);
659 authority3.is_blocklisted = true;
660 assert_eq!(
661 BridgeCommittee::new(vec![authority1, authority2, authority3])
662 .unwrap()
663 .total_blocklisted_stake(),
664 3000
665 );
666
667 Ok(())
668 }
669
670 #[test]
672 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
673 fn test_bridge_action_approval_threshold_regression_test() -> anyhow::Result<()> {
674 let action = get_test_iota_to_eth_bridge_action(None, None, None, None, None, None, None);
675 assert_eq!(action.approval_threshold(), 3334);
676
677 let action = get_test_eth_to_iota_bridge_action(None, None, None, None);
678 assert_eq!(action.approval_threshold(), 3334);
679
680 let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
681 nonce: 94,
682 chain_id: BridgeChainId::EthSepolia,
683 blocklist_type: BlocklistType::Unblocklist,
684 members_to_update: vec![],
685 });
686 assert_eq!(action.approval_threshold(), 5001);
687
688 let action = BridgeAction::EmergencyAction(EmergencyAction {
689 nonce: 56,
690 chain_id: BridgeChainId::EthSepolia,
691 action_type: EmergencyActionType::Pause,
692 });
693 assert_eq!(action.approval_threshold(), 450);
694
695 let action = BridgeAction::EmergencyAction(EmergencyAction {
696 nonce: 56,
697 chain_id: BridgeChainId::EthSepolia,
698 action_type: EmergencyActionType::Unpause,
699 });
700 assert_eq!(action.approval_threshold(), 5001);
701
702 let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
703 nonce: 15,
704 chain_id: BridgeChainId::IotaCustom,
705 sending_chain_id: BridgeChainId::EthCustom,
706 new_usd_limit: 1_000_000 * USD_MULTIPLIER,
707 });
708 assert_eq!(action.approval_threshold(), 5001);
709
710 let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
711 nonce: 266,
712 chain_id: BridgeChainId::IotaCustom,
713 token_id: TOKEN_ID_BTC,
714 new_usd_price: 100_000 * USD_MULTIPLIER,
715 });
716 assert_eq!(action.approval_threshold(), 5001);
717
718 let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
719 nonce: 123,
720 chain_id: BridgeChainId::EthCustom,
721 proxy_address: EthAddress::repeat_byte(6),
722 new_impl_address: EthAddress::repeat_byte(9),
723 call_data: vec![],
724 });
725 assert_eq!(action.approval_threshold(), 5001);
726 Ok(())
727 }
728
729 #[test]
730 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
731 fn test_bridge_committee_filter_blocklisted_authorities() -> anyhow::Result<()> {
732 let (authority1, _, _) = get_test_authority_and_key(5000, 9999);
734 let (mut authority2, _, _) = get_test_authority_and_key(3000, 9999);
735 authority2.is_blocklisted = true;
736 let (authority3, _, _) = get_test_authority_and_key(2000, 9999);
737 let committee = BridgeCommittee::new(vec![
738 authority1.clone(),
739 authority2.clone(),
740 authority3.clone(),
741 ])
742 .unwrap();
743
744 let result = committee
746 .shuffle_by_stake(None, None)
747 .into_iter()
748 .collect::<HashSet<_>>();
749 assert_eq!(
750 HashSet::from_iter(vec![authority1.pubkey_bytes(), authority3.pubkey_bytes()]),
751 result
752 );
753
754 let result = committee
756 .shuffle_by_stake(
757 None,
758 Some(
759 &[authority1.pubkey_bytes(), authority2.pubkey_bytes()]
760 .iter()
761 .cloned()
762 .collect(),
763 ),
764 )
765 .into_iter()
766 .collect::<HashSet<_>>();
767 assert_eq!(HashSet::from_iter(vec![authority1.pubkey_bytes()]), result);
768
769 Ok(())
770 }
771}