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