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