iota_bridge/server/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5#![allow(clippy::inconsistent_digit_grouping)]
6use std::{net::SocketAddr, str::FromStr, sync::Arc};
7
8use axum::{
9    Json, Router,
10    extract::{Path, State},
11    http::StatusCode,
12    routing::get,
13};
14use ethers::types::Address as EthAddress;
15use fastcrypto::{
16    encoding::{Encoding, Hex},
17    traits::ToFromBytes,
18};
19use iota_types::{TypeTag, bridge::BridgeChainId};
20use tracing::{info, instrument};
21
22use crate::{
23    crypto::BridgeAuthorityPublicKeyBytes,
24    error::BridgeError,
25    metrics::BridgeMetrics,
26    server::handler::{BridgeRequestHandler, BridgeRequestHandlerTrait},
27    types::{
28        AddTokensOnEvmAction, AddTokensOnIotaAction, AssetPriceUpdateAction,
29        BlocklistCommitteeAction, BlocklistType, BridgeAction, EmergencyAction,
30        EmergencyActionType, EvmContractUpgradeAction, LimitUpdateAction, SignedBridgeAction,
31    },
32    with_metrics,
33};
34
35pub mod governance_verifier;
36pub mod handler;
37
38#[cfg(test)]
39pub(crate) mod mock_handler;
40
41pub const APPLICATION_JSON: &str = "application/json";
42
43pub const PING_PATH: &str = "/ping";
44
45// Important: for BridgeActions, the paths need to match the ones in
46// bridge_client.rs
47pub const ETH_TO_IOTA_TX_PATH: &str = "/sign/bridge_tx/eth/iota/:tx_hash/:event_index";
48pub const IOTA_TO_ETH_TX_PATH: &str = "/sign/bridge_tx/iota/eth/:tx_digest/:event_index";
49pub const COMMITTEE_BLOCKLIST_UPDATE_PATH: &str =
50    "/sign/update_committee_blocklist/:chain_id/:nonce/:type/:keys";
51pub const EMERGENCY_BUTTON_PATH: &str = "/sign/emergency_button/:chain_id/:nonce/:type";
52pub const LIMIT_UPDATE_PATH: &str =
53    "/sign/update_limit/:chain_id/:nonce/:sending_chain_id/:new_usd_limit";
54pub const ASSET_PRICE_UPDATE_PATH: &str =
55    "/sign/update_asset_price/:chain_id/:nonce/:token_id/:new_usd_price";
56pub const EVM_CONTRACT_UPGRADE_PATH_WITH_CALLDATA: &str =
57    "/sign/upgrade_evm_contract/:chain_id/:nonce/:proxy_address/:new_impl_address/:calldata";
58pub const EVM_CONTRACT_UPGRADE_PATH: &str =
59    "/sign/upgrade_evm_contract/:chain_id/:nonce/:proxy_address/:new_impl_address";
60pub const ADD_TOKENS_ON_IOTA_PATH: &str =
61    "/sign/add_tokens_on_iota/:chain_id/:nonce/:native/:token_ids/:token_type_names/:token_prices";
62pub const ADD_TOKENS_ON_EVM_PATH: &str = "/sign/add_tokens_on_evm/:chain_id/:nonce/:native/:token_ids/:token_addresses/:token_iota_decimals/:token_prices";
63
64/// BridgeNode's public metadata that is acceesible via the `/ping` endpoint.
65// Be careful with what to put here, as it is public.
66#[derive(serde::Serialize, Clone)]
67pub struct BridgeNodePublicMetadata {
68    pub version: Option<String>,
69}
70
71impl BridgeNodePublicMetadata {
72    pub fn new(version: String) -> Self {
73        Self {
74            version: Some(version),
75        }
76    }
77
78    pub fn empty_for_testing() -> Self {
79        Self { version: None }
80    }
81}
82
83pub fn run_server(
84    socket_address: &SocketAddr,
85    handler: BridgeRequestHandler,
86    metrics: Arc<BridgeMetrics>,
87    metadata: Arc<BridgeNodePublicMetadata>,
88) -> tokio::task::JoinHandle<()> {
89    let socket_address = *socket_address;
90    tokio::spawn(async move {
91        let listener = tokio::net::TcpListener::bind(socket_address).await.unwrap();
92        axum::serve(
93            listener,
94            make_router(Arc::new(handler), metrics, metadata).into_make_service(),
95        )
96        .await
97        .unwrap();
98    })
99}
100
101pub(crate) fn make_router(
102    handler: Arc<impl BridgeRequestHandlerTrait + Sync + Send + 'static>,
103    metrics: Arc<BridgeMetrics>,
104    metadata: Arc<BridgeNodePublicMetadata>,
105) -> Router {
106    Router::new()
107        .route("/", get(health_check))
108        .route(PING_PATH, get(ping))
109        .route(ETH_TO_IOTA_TX_PATH, get(handle_eth_tx_hash))
110        .route(IOTA_TO_ETH_TX_PATH, get(handle_iota_tx_digest))
111        .route(
112            COMMITTEE_BLOCKLIST_UPDATE_PATH,
113            get(handle_update_committee_blocklist_action),
114        )
115        .route(EMERGENCY_BUTTON_PATH, get(handle_emergency_action))
116        .route(LIMIT_UPDATE_PATH, get(handle_limit_update_action))
117        .route(
118            ASSET_PRICE_UPDATE_PATH,
119            get(handle_asset_price_update_action),
120        )
121        .route(EVM_CONTRACT_UPGRADE_PATH, get(handle_evm_contract_upgrade))
122        .route(
123            EVM_CONTRACT_UPGRADE_PATH_WITH_CALLDATA,
124            get(handle_evm_contract_upgrade_with_calldata),
125        )
126        .route(ADD_TOKENS_ON_IOTA_PATH, get(handle_add_tokens_on_iota))
127        .route(ADD_TOKENS_ON_EVM_PATH, get(handle_add_tokens_on_evm))
128        .with_state((handler, metrics, metadata))
129}
130
131impl axum::response::IntoResponse for BridgeError {
132    // TODO: distinguish client error.
133    fn into_response(self) -> axum::response::Response {
134        (
135            StatusCode::INTERNAL_SERVER_ERROR,
136            format!("Something went wrong: {:?}", self),
137        )
138            .into_response()
139    }
140}
141
142impl<E> From<E> for BridgeError
143where
144    E: Into<anyhow::Error>,
145{
146    fn from(err: E) -> Self {
147        Self::Generic(err.into().to_string())
148    }
149}
150
151async fn health_check() -> StatusCode {
152    StatusCode::OK
153}
154
155async fn ping(
156    State((_handler, _metrics, metadata)): State<(
157        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
158        Arc<BridgeMetrics>,
159        Arc<BridgeNodePublicMetadata>,
160    )>,
161) -> Result<Json<BridgeNodePublicMetadata>, BridgeError> {
162    Ok(Json(metadata.as_ref().clone()))
163}
164
165#[instrument(level = "error", skip_all, fields(tx_hash_hex=tx_hash_hex, event_idx=event_idx))]
166async fn handle_eth_tx_hash(
167    Path((tx_hash_hex, event_idx)): Path<(String, u16)>,
168    State((handler, metrics, _metadata)): State<(
169        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
170        Arc<BridgeMetrics>,
171        Arc<BridgeNodePublicMetadata>,
172    )>,
173) -> Result<Json<SignedBridgeAction>, BridgeError> {
174    let future = async {
175        let sig = handler.handle_eth_tx_hash(tx_hash_hex, event_idx).await?;
176        Ok(sig)
177    };
178    with_metrics!(metrics.clone(), "handle_eth_tx_hash", future).await
179}
180
181#[instrument(level = "error", skip_all, fields(tx_digest_base58=tx_digest_base58, event_idx=event_idx))]
182async fn handle_iota_tx_digest(
183    Path((tx_digest_base58, event_idx)): Path<(String, u16)>,
184    State((handler, metrics, _metadata)): State<(
185        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
186        Arc<BridgeMetrics>,
187        Arc<BridgeNodePublicMetadata>,
188    )>,
189) -> Result<Json<SignedBridgeAction>, BridgeError> {
190    let future = async {
191        let sig: Json<SignedBridgeAction> = handler
192            .handle_iota_tx_digest(tx_digest_base58, event_idx)
193            .await?;
194        Ok(sig)
195    };
196    with_metrics!(metrics.clone(), "handle_iota_tx_digest", future).await
197}
198
199#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, blocklist_type=blocklist_type, keys=keys))]
200async fn handle_update_committee_blocklist_action(
201    Path((chain_id, nonce, blocklist_type, keys)): Path<(u8, u64, u8, String)>,
202    State((handler, metrics, _metadata)): State<(
203        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
204        Arc<BridgeMetrics>,
205        Arc<BridgeNodePublicMetadata>,
206    )>,
207) -> Result<Json<SignedBridgeAction>, BridgeError> {
208    let future = async {
209        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
210            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
211        })?;
212        let blocklist_type = BlocklistType::try_from(blocklist_type).map_err(|err| {
213            BridgeError::InvalidBridgeClientRequest(format!(
214                "Invalid blocklist action type: {:?}",
215                err
216            ))
217        })?;
218        let members_to_update = keys
219            .split(',')
220            .map(|s| {
221                let bytes = Hex::decode(s).map_err(|e| anyhow::anyhow!("{:?}", e))?;
222                BridgeAuthorityPublicKeyBytes::from_bytes(&bytes)
223                    .map_err(|e| anyhow::anyhow!("{:?}", e))
224            })
225            .collect::<Result<Vec<_>, _>>()
226            .map_err(|e| BridgeError::InvalidBridgeClientRequest(format!("{:?}", e)))?;
227        let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
228            chain_id,
229            nonce,
230            blocklist_type,
231            members_to_update,
232        });
233
234        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
235        Ok(sig)
236    };
237    with_metrics!(
238        metrics.clone(),
239        "handle_update_committee_blocklist_action",
240        future
241    )
242    .await
243}
244
245#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, action_type=action_type))]
246async fn handle_emergency_action(
247    Path((chain_id, nonce, action_type)): Path<(u8, u64, u8)>,
248    State((handler, metrics, _metadata)): State<(
249        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
250        Arc<BridgeMetrics>,
251        Arc<BridgeNodePublicMetadata>,
252    )>,
253) -> Result<Json<SignedBridgeAction>, BridgeError> {
254    let future = async {
255        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
256            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
257        })?;
258        let action_type = EmergencyActionType::try_from(action_type).map_err(|err| {
259            BridgeError::InvalidBridgeClientRequest(format!(
260                "Invalid emergency action type: {:?}",
261                err
262            ))
263        })?;
264        let action = BridgeAction::EmergencyAction(EmergencyAction {
265            chain_id,
266            nonce,
267            action_type,
268        });
269        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
270        Ok(sig)
271    };
272    with_metrics!(metrics.clone(), "handle_emergency_action", future).await
273}
274
275#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, sending_chain_id=sending_chain_id, new_usd_limit=new_usd_limit))]
276async fn handle_limit_update_action(
277    Path((chain_id, nonce, sending_chain_id, new_usd_limit)): Path<(u8, u64, u8, u64)>,
278    State((handler, metrics, _metadata)): State<(
279        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
280        Arc<BridgeMetrics>,
281        Arc<BridgeNodePublicMetadata>,
282    )>,
283) -> Result<Json<SignedBridgeAction>, BridgeError> {
284    let future = async {
285        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
286            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
287        })?;
288        let sending_chain_id = BridgeChainId::try_from(sending_chain_id).map_err(|err| {
289            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
290        })?;
291        let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
292            chain_id,
293            nonce,
294            sending_chain_id,
295            new_usd_limit,
296        });
297        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
298        Ok(sig)
299    };
300    with_metrics!(metrics.clone(), "handle_limit_update_action", future).await
301}
302
303#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, token_id=token_id, new_usd_price=new_usd_price))]
304async fn handle_asset_price_update_action(
305    Path((chain_id, nonce, token_id, new_usd_price)): Path<(u8, u64, u8, u64)>,
306    State((handler, metrics, _metadata)): State<(
307        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
308        Arc<BridgeMetrics>,
309        Arc<BridgeNodePublicMetadata>,
310    )>,
311) -> Result<Json<SignedBridgeAction>, BridgeError> {
312    let future = async {
313        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
314            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
315        })?;
316        let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
317            chain_id,
318            nonce,
319            token_id,
320            new_usd_price,
321        });
322        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
323        Ok(sig)
324    };
325    with_metrics!(metrics.clone(), "handle_asset_price_update_action", future).await
326}
327
328#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, proxy_address=format!("{:x}", proxy_address), new_impl_address=format!("{:x}", new_impl_address)))]
329async fn handle_evm_contract_upgrade_with_calldata(
330    Path((chain_id, nonce, proxy_address, new_impl_address, calldata)): Path<(
331        u8,
332        u64,
333        EthAddress,
334        EthAddress,
335        String,
336    )>,
337    State((handler, metrics, _metadata)): State<(
338        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
339        Arc<BridgeMetrics>,
340        Arc<BridgeNodePublicMetadata>,
341    )>,
342) -> Result<Json<SignedBridgeAction>, BridgeError> {
343    let future = async {
344        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
345            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
346        })?;
347        let call_data = Hex::decode(&calldata).map_err(|e| {
348            BridgeError::InvalidBridgeClientRequest(format!("Invalid call data: {:?}", e))
349        })?;
350        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
351            chain_id,
352            nonce,
353            proxy_address,
354            new_impl_address,
355            call_data,
356        });
357        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
358        Ok(sig)
359    };
360    with_metrics!(
361        metrics.clone(),
362        "handle_evm_contract_upgrade_with_calldata",
363        future
364    )
365    .await
366}
367
368#[instrument(
369    level = "error",
370    skip_all,
371    fields(chain_id, nonce, proxy_address, new_impl_address)
372)]
373async fn handle_evm_contract_upgrade(
374    Path((chain_id, nonce, proxy_address, new_impl_address)): Path<(
375        u8,
376        u64,
377        EthAddress,
378        EthAddress,
379    )>,
380    State((handler, metrics, _metadata)): State<(
381        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
382        Arc<BridgeMetrics>,
383        Arc<BridgeNodePublicMetadata>,
384    )>,
385) -> Result<Json<SignedBridgeAction>, BridgeError> {
386    let future = async {
387        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
388            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
389        })?;
390        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
391            chain_id,
392            nonce,
393            proxy_address,
394            new_impl_address,
395            call_data: vec![],
396        });
397        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
398
399        Ok(sig)
400    };
401    with_metrics!(metrics.clone(), "handle_evm_contract_upgrade", future).await
402}
403
404#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, native=native, token_ids=token_ids, token_type_names=token_type_names, token_prices=token_prices))]
405async fn handle_add_tokens_on_iota(
406    Path((chain_id, nonce, native, token_ids, token_type_names, token_prices)): Path<(
407        u8,
408        u64,
409        u8,
410        String,
411        String,
412        String,
413    )>,
414    State((handler, metrics, _metadata)): State<(
415        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
416        Arc<BridgeMetrics>,
417        Arc<BridgeNodePublicMetadata>,
418    )>,
419) -> Result<Json<SignedBridgeAction>, BridgeError> {
420    let future = async {
421        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
422            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
423        })?;
424
425        if !chain_id.is_iota_chain() {
426            return Err(BridgeError::InvalidBridgeClientRequest(
427                "handle_add_tokens_on_iota only expects IOTA chain id".to_string(),
428            ));
429        }
430
431        let native = match native {
432            1 => true,
433            0 => false,
434            _ => {
435                return Err(BridgeError::InvalidBridgeClientRequest(format!(
436                    "Invalid native flag: {}",
437                    native
438                )));
439            }
440        };
441        let token_ids = token_ids
442            .split(',')
443            .map(|s| {
444                s.parse::<u8>().map_err(|err| {
445                    BridgeError::InvalidBridgeClientRequest(format!("Invalid token id: {:?}", err))
446                })
447            })
448            .collect::<Result<Vec<_>, _>>()?;
449        let token_type_names = token_type_names
450            .split(',')
451            .map(|s| {
452                TypeTag::from_str(s).map_err(|err| {
453                    BridgeError::InvalidBridgeClientRequest(format!(
454                        "Invalid token type name: {:?}",
455                        err
456                    ))
457                })
458            })
459            .collect::<Result<Vec<_>, _>>()?;
460        let token_prices = token_prices
461            .split(',')
462            .map(|s| {
463                s.parse::<u64>().map_err(|err| {
464                    BridgeError::InvalidBridgeClientRequest(format!(
465                        "Invalid token price: {:?}",
466                        err
467                    ))
468                })
469            })
470            .collect::<Result<Vec<_>, _>>()?;
471        let action = BridgeAction::AddTokensOnIotaAction(AddTokensOnIotaAction {
472            chain_id,
473            nonce,
474            native,
475            token_ids,
476            token_type_names,
477            token_prices,
478        });
479        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
480        Ok(sig)
481    };
482    with_metrics!(metrics.clone(), "handle_add_tokens_on_iota", future).await
483}
484
485#[instrument(level = "error", skip_all, fields(chain_id=chain_id, nonce=nonce, native=native, token_ids=token_ids, token_addresses=token_addresses, token_iota_decimals=token_iota_decimals, token_prices=token_prices))]
486async fn handle_add_tokens_on_evm(
487    Path((chain_id, nonce, native, token_ids, token_addresses, token_iota_decimals, token_prices)): Path<(
488        u8,
489        u64,
490        u8,
491        String,
492        String,
493        String,
494        String,
495    )>,
496    State((handler, metrics, _metadata)): State<(
497        Arc<impl BridgeRequestHandlerTrait + Sync + Send>,
498        Arc<BridgeMetrics>,
499        Arc<BridgeNodePublicMetadata>,
500    )>,
501) -> Result<Json<SignedBridgeAction>, BridgeError> {
502    let future = async {
503        let chain_id = BridgeChainId::try_from(chain_id).map_err(|err| {
504            BridgeError::InvalidBridgeClientRequest(format!("Invalid chain id: {:?}", err))
505        })?;
506        if chain_id.is_iota_chain() {
507            return Err(BridgeError::InvalidBridgeClientRequest(
508                "handle_add_tokens_on_evm does not expect IOTA chain id".to_string(),
509            ));
510        }
511
512        let native = match native {
513            1 => true,
514            0 => false,
515            _ => {
516                return Err(BridgeError::InvalidBridgeClientRequest(format!(
517                    "Invalid native flag: {}",
518                    native
519                )));
520            }
521        };
522        let token_ids = token_ids
523            .split(',')
524            .map(|s| {
525                s.parse::<u8>().map_err(|err| {
526                    BridgeError::InvalidBridgeClientRequest(format!("Invalid token id: {:?}", err))
527                })
528            })
529            .collect::<Result<Vec<_>, _>>()?;
530        let token_addresses = token_addresses
531            .split(',')
532            .map(|s| {
533                EthAddress::from_str(s).map_err(|err| {
534                    BridgeError::InvalidBridgeClientRequest(format!(
535                        "Invalid token address: {:?}",
536                        err
537                    ))
538                })
539            })
540            .collect::<Result<Vec<_>, _>>()?;
541        let token_iota_decimals = token_iota_decimals
542            .split(',')
543            .map(|s| {
544                s.parse::<u8>().map_err(|err| {
545                    BridgeError::InvalidBridgeClientRequest(format!(
546                        "Invalid token iota decimals: {:?}",
547                        err
548                    ))
549                })
550            })
551            .collect::<Result<Vec<_>, _>>()?;
552        let token_prices = token_prices
553            .split(',')
554            .map(|s| {
555                s.parse::<u64>().map_err(|err| {
556                    BridgeError::InvalidBridgeClientRequest(format!(
557                        "Invalid token price: {:?}",
558                        err
559                    ))
560                })
561            })
562            .collect::<Result<Vec<_>, _>>()?;
563        let action = BridgeAction::AddTokensOnEvmAction(AddTokensOnEvmAction {
564            chain_id,
565            nonce,
566            native,
567            token_ids,
568            token_addresses,
569            token_iota_decimals,
570            token_prices,
571        });
572        let sig: Json<SignedBridgeAction> = handler.handle_governance_action(action).await?;
573        Ok(sig)
574    };
575    with_metrics!(metrics.clone(), "handle_add_tokens_on_evm", future).await
576}
577
578#[macro_export]
579macro_rules! with_metrics {
580    ($metrics:expr, $type_:expr, $func:expr) => {
581        async move {
582            info!("Received {} request", $type_);
583            $metrics
584                .requests_received
585                .with_label_values(&[$type_])
586                .inc();
587            $metrics
588                .requests_inflight
589                .with_label_values(&[$type_])
590                .inc();
591
592            let result = $func.await;
593
594            match &result {
595                Ok(_) => {
596                    info!("{} request succeeded", $type_);
597                    $metrics.requests_ok.with_label_values(&[$type_]).inc();
598                }
599                Err(e) => {
600                    info!("{} request failed: {:?}", $type_, e);
601                    $metrics.err_requests.with_label_values(&[$type_]).inc();
602                }
603            }
604
605            $metrics
606                .requests_inflight
607                .with_label_values(&[$type_])
608                .dec();
609            result
610        }
611    };
612}
613
614#[cfg(test)]
615mod tests {
616    use iota_types::bridge::TOKEN_ID_BTC;
617
618    use super::*;
619    use crate::{
620        client::bridge_client::BridgeClient, server::mock_handler::BridgeRequestMockHandler,
621        test_utils::get_test_authorities_and_run_mock_bridge_server, types::BridgeCommittee,
622    };
623
624    #[tokio::test]
625    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
626    async fn test_bridge_server_handle_blocklist_update_action_path() {
627        let client = setup();
628
629        let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
630            &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
631                .unwrap(),
632        )
633        .unwrap();
634        let action = BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
635            nonce: 129,
636            chain_id: BridgeChainId::IotaCustom,
637            blocklist_type: BlocklistType::Blocklist,
638            members_to_update: vec![pub_key_bytes.clone()],
639        });
640        client.request_sign_bridge_action(action).await.unwrap();
641    }
642
643    #[tokio::test]
644    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
645    async fn test_bridge_server_handle_emergency_action_path() {
646        let client = setup();
647
648        let action = BridgeAction::EmergencyAction(EmergencyAction {
649            nonce: 55,
650            chain_id: BridgeChainId::IotaCustom,
651            action_type: EmergencyActionType::Pause,
652        });
653        client.request_sign_bridge_action(action).await.unwrap();
654    }
655
656    #[tokio::test]
657    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
658    async fn test_bridge_server_handle_limit_update_action_path() {
659        let client = setup();
660
661        let action = BridgeAction::LimitUpdateAction(LimitUpdateAction {
662            nonce: 15,
663            chain_id: BridgeChainId::IotaCustom,
664            sending_chain_id: BridgeChainId::EthCustom,
665            new_usd_limit: 1_000_000_0000, // $1M USD
666        });
667        client.request_sign_bridge_action(action).await.unwrap();
668    }
669
670    #[tokio::test]
671    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
672    async fn test_bridge_server_handle_asset_price_update_action_path() {
673        let client = setup();
674
675        let action = BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
676            nonce: 266,
677            chain_id: BridgeChainId::IotaCustom,
678            token_id: TOKEN_ID_BTC,
679            new_usd_price: 100_000_0000, // $100k USD
680        });
681        client.request_sign_bridge_action(action).await.unwrap();
682    }
683
684    #[tokio::test]
685    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
686    async fn test_bridge_server_handle_evm_contract_upgrade_action_path() {
687        let client = setup();
688
689        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
690            nonce: 123,
691            chain_id: BridgeChainId::EthCustom,
692            proxy_address: EthAddress::repeat_byte(6),
693            new_impl_address: EthAddress::repeat_byte(9),
694            call_data: vec![],
695        });
696        client.request_sign_bridge_action(action).await.unwrap();
697
698        let action = BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
699            nonce: 123,
700            chain_id: BridgeChainId::EthCustom,
701            proxy_address: EthAddress::repeat_byte(6),
702            new_impl_address: EthAddress::repeat_byte(9),
703            call_data: vec![12, 34, 56],
704        });
705        client.request_sign_bridge_action(action).await.unwrap();
706    }
707
708    #[tokio::test]
709    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
710    async fn test_bridge_server_handle_add_tokens_on_iota_action_path() {
711        let client = setup();
712
713        let action = BridgeAction::AddTokensOnIotaAction(AddTokensOnIotaAction {
714            nonce: 266,
715            chain_id: BridgeChainId::IotaCustom,
716            native: false,
717            token_ids: vec![100, 101, 102],
718            token_type_names: vec![
719                TypeTag::from_str("0x0000000000000000000000000000000000000000000000000000000000000abc::my_coin::MyCoin1").unwrap(),
720                TypeTag::from_str("0x0000000000000000000000000000000000000000000000000000000000000abc::my_coin::MyCoin2").unwrap(),
721                TypeTag::from_str("0x0000000000000000000000000000000000000000000000000000000000000abc::my_coin::MyCoin3").unwrap(),
722            ],
723            token_prices: vec![100_000_0000, 200_000_0000, 300_000_0000],
724        });
725        client.request_sign_bridge_action(action).await.unwrap();
726    }
727
728    #[tokio::test]
729    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
730    async fn test_bridge_server_handle_add_tokens_on_evm_action_path() {
731        let client = setup();
732
733        let action = BridgeAction::AddTokensOnEvmAction(crate::types::AddTokensOnEvmAction {
734            nonce: 0,
735            chain_id: BridgeChainId::EthCustom,
736            native: false,
737            token_ids: vec![99, 100, 101],
738            token_addresses: vec![
739                EthAddress::repeat_byte(1),
740                EthAddress::repeat_byte(2),
741                EthAddress::repeat_byte(3),
742            ],
743            token_iota_decimals: vec![5, 6, 7],
744            token_prices: vec![1_000_000_000, 2_000_000_000, 3_000_000_000],
745        });
746        client.request_sign_bridge_action(action).await.unwrap();
747    }
748
749    fn setup() -> BridgeClient {
750        let mock = BridgeRequestMockHandler::new();
751        let (_handles, authorities, mut secrets) =
752            get_test_authorities_and_run_mock_bridge_server(vec![10000], vec![mock.clone()]);
753        mock.set_signer(secrets.swap_remove(0));
754        let committee = BridgeCommittee::new(authorities).unwrap();
755        let pub_key = committee.members().keys().next().unwrap();
756        BridgeClient::new(pub_key.clone(), Arc::new(committee)).unwrap()
757    }
758}