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