1use crate::abi::{
5 EthBridgeCommittee, EthBridgeConfig, EthBridgeLimiter, EthBridgeVault, EthSuiBridge,
6};
7use crate::config::{
8 BridgeNodeConfig, EthConfig, MetricsConfig, SuiConfig, WatchdogConfig, default_ed25519_key_pair,
9};
10use crate::crypto::{BridgeAuthorityKeyPair, BridgeAuthorityPublicKeyBytes};
11use crate::server::APPLICATION_JSON;
12use crate::types::{AddTokensOnSuiAction, BridgeAction, BridgeCommittee};
13use alloy::network::EthereumWallet;
14use alloy::primitives::Address as EthAddress;
15use alloy::providers::{ProviderBuilder, RootProvider, WsConnect};
16use alloy::signers::local::PrivateKeySigner;
17use anyhow::anyhow;
18use fastcrypto::ed25519::Ed25519KeyPair;
19use fastcrypto::encoding::{Encoding, Hex};
20use fastcrypto::secp256k1::Secp256k1KeyPair;
21use fastcrypto::traits::{EncodeDecodeBase64, KeyPair};
22use futures::future::join_all;
23use std::collections::BTreeMap;
24use std::path::PathBuf;
25use std::str::FromStr;
26use std::sync::Arc;
27use sui_config::Config;
28use sui_json_rpc_types::{
29 SuiExecutionStatus, SuiTransactionBlockEffectsAPI, SuiTransactionBlockResponseOptions,
30};
31use sui_keys::keypair_file::read_key;
32use sui_sdk::wallet_context::WalletContext;
33use sui_test_transaction_builder::TestTransactionBuilder;
34use sui_types::BRIDGE_PACKAGE_ID;
35use sui_types::base_types::SuiAddress;
36use sui_types::bridge::{
37 BRIDGE_MODULE_NAME, BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME, BridgeChainId,
38};
39use sui_types::committee::StakeUnit;
40use sui_types::crypto::{SuiKeyPair, ToFromBytes, get_key_pair};
41use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
42use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary;
43use sui_types::transaction::{ObjectArg, TransactionData};
44use url::Url;
45
46pub struct EthBridgeContracts {
47 pub bridge: EthSuiBridge::EthSuiBridgeInstance<EthProvider>,
48 pub committee: EthBridgeCommittee::EthBridgeCommitteeInstance<EthProvider>,
49 pub limiter: EthBridgeLimiter::EthBridgeLimiterInstance<EthProvider>,
50 pub vault: EthBridgeVault::EthBridgeVaultInstance<EthProvider>,
51 pub config: EthBridgeConfig::EthBridgeConfigInstance<EthProvider>,
52}
53
54pub type EthProvider = Arc<RootProvider<alloy::network::Ethereum>>;
55pub type EthSignerProvider = Arc<
56 alloy::providers::fillers::FillProvider<
57 alloy::providers::fillers::JoinFill<
58 alloy::providers::fillers::JoinFill<
59 alloy::providers::Identity,
60 alloy::providers::fillers::JoinFill<
61 alloy::providers::fillers::GasFiller,
62 alloy::providers::fillers::JoinFill<
63 alloy::providers::fillers::BlobGasFiller,
64 alloy::providers::fillers::JoinFill<
65 alloy::providers::fillers::NonceFiller,
66 alloy::providers::fillers::ChainIdFiller,
67 >,
68 >,
69 >,
70 >,
71 alloy::providers::fillers::WalletFiller<EthereumWallet>,
72 >,
73 EthProvider,
74 alloy::network::Ethereum,
75 >,
76>;
77pub type EthWsProvider = Arc<
78 alloy::providers::fillers::FillProvider<
79 alloy::providers::fillers::JoinFill<
80 alloy::providers::Identity,
81 alloy::providers::fillers::JoinFill<
82 alloy::providers::fillers::GasFiller,
83 alloy::providers::fillers::JoinFill<
84 alloy::providers::fillers::BlobGasFiller,
85 alloy::providers::fillers::JoinFill<
86 alloy::providers::fillers::NonceFiller,
87 alloy::providers::fillers::ChainIdFiller,
88 >,
89 >,
90 >,
91 >,
92 alloy::providers::RootProvider<alloy::network::Ethereum>,
93 alloy::network::Ethereum,
94 >,
95>;
96
97pub fn get_eth_provider(url: &str) -> anyhow::Result<EthProvider> {
98 let url = Url::parse(url).map_err(|e| anyhow!("Invalid RPC URL: {}", e))?;
99 let provider = RootProvider::new_http(url);
100 Ok(Arc::new(provider))
101}
102
103pub fn get_eth_signer_provider(
104 url: &str,
105 private_key_hex: &str,
106) -> anyhow::Result<EthSignerProvider> {
107 let signer = PrivateKeySigner::from_str(private_key_hex)
108 .map_err(|e| anyhow!("Invalid private key: {}", e))?;
109 let wallet = EthereumWallet::from(signer);
110 let provider = ProviderBuilder::new()
111 .wallet(wallet)
112 .connect_provider(get_eth_provider(url)?);
113 Ok(Arc::new(provider))
114}
115
116pub async fn get_eth_ws_provider(url: &str) -> anyhow::Result<EthWsProvider> {
117 let url = Url::parse(url).map_err(|e| anyhow!("Invalid WebSocket URL: {}", e))?;
118 let ws = WsConnect::new(url);
119 let provider = ProviderBuilder::new().connect_ws(ws).await?;
120 Ok(Arc::new(provider))
121}
122
123pub fn generate_bridge_authority_key_and_write_to_file(
125 path: &PathBuf,
126) -> Result<(), anyhow::Error> {
127 let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
128 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
129 println!(
130 "Corresponding Ethereum address by this ecdsa key: {:?}",
131 eth_address
132 );
133 let sui_address = SuiAddress::from(&kp.public);
134 println!(
135 "Corresponding Sui address by this ecdsa key: {:?}",
136 sui_address
137 );
138 let base64_encoded = kp.encode_base64();
139 std::fs::write(path, base64_encoded)
140 .map_err(|err| anyhow!("Failed to write encoded key to path: {:?}", err))
141}
142
143pub fn generate_bridge_client_key_and_write_to_file(
145 path: &PathBuf,
146 use_ecdsa: bool,
147) -> Result<(), anyhow::Error> {
148 let kp = if use_ecdsa {
149 let (_, kp): (_, Secp256k1KeyPair) = get_key_pair();
150 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
151 println!(
152 "Corresponding Ethereum address by this ecdsa key: {:?}",
153 eth_address
154 );
155 SuiKeyPair::from(kp)
156 } else {
157 let (_, kp): (_, Ed25519KeyPair) = get_key_pair();
158 SuiKeyPair::from(kp)
159 };
160 let sui_address = SuiAddress::from(&kp.public());
161 println!("Corresponding Sui address by this key: {:?}", sui_address);
162
163 let contents = kp.encode_base64();
164 std::fs::write(path, contents)
165 .map_err(|err| anyhow!("Failed to write encoded key to path: {:?}", err))
166}
167
168pub async fn get_eth_contract_addresses(
170 bridge_proxy_address: EthAddress,
171 provider: EthProvider,
172) -> anyhow::Result<(
173 EthAddress,
174 EthAddress,
175 EthAddress,
176 EthAddress,
177 EthAddress,
178 EthAddress,
179 EthAddress,
180 EthAddress,
181)> {
182 let sui_bridge = EthSuiBridge::new(bridge_proxy_address, provider.clone());
183 let committee_address: EthAddress = sui_bridge.committee().call().await?;
184 let committee = EthBridgeCommittee::new(committee_address, provider.clone());
185 let config_address: EthAddress = committee.config().call().await?;
186 let bridge_config = EthBridgeConfig::new(config_address, provider.clone());
187 let limiter_address: EthAddress = sui_bridge.limiter().call().await?;
188 let vault_address: EthAddress = sui_bridge.vault().call().await?;
189 let vault = EthBridgeVault::new(vault_address, provider.clone());
190 let weth_address: EthAddress = vault.wETH().call().await?;
191 let usdt_address: EthAddress = bridge_config.tokenAddressOf(4).call().await?;
192 let wbtc_address: EthAddress = bridge_config.tokenAddressOf(1).call().await?;
193 let lbtc_address: EthAddress = bridge_config.tokenAddressOf(6).call().await?;
194
195 Ok((
196 committee_address,
197 limiter_address,
198 vault_address,
199 config_address,
200 weth_address,
201 usdt_address,
202 wbtc_address,
203 lbtc_address,
204 ))
205}
206
207pub async fn get_eth_contracts(
209 bridge_proxy_address: EthAddress,
210 provider: EthProvider,
211) -> anyhow::Result<EthBridgeContracts> {
212 let sui_bridge = EthSuiBridge::new(bridge_proxy_address, provider.clone());
213 let committee_address: EthAddress = sui_bridge.committee().call().await?;
214 let limiter_address: EthAddress = sui_bridge.limiter().call().await?;
215 let vault_address: EthAddress = sui_bridge.vault().call().await?;
216 let committee = EthBridgeCommittee::new(committee_address, provider.clone());
217 let config_address: EthAddress = committee.config().call().await?;
218
219 let limiter = EthBridgeLimiter::new(limiter_address, provider.clone());
220 let vault = EthBridgeVault::new(vault_address, provider.clone());
221 let config = EthBridgeConfig::new(config_address, provider.clone());
222 Ok(EthBridgeContracts {
223 bridge: sui_bridge,
224 committee,
225 limiter,
226 vault,
227 config,
228 })
229}
230
231pub fn examine_key(path: &PathBuf, is_validator_key: bool) -> Result<(), anyhow::Error> {
234 let key = read_key(path, is_validator_key)?;
235 let sui_address = SuiAddress::from(&key.public());
236 let pubkey = match key {
237 SuiKeyPair::Secp256k1(kp) => {
238 println!("Secp256k1 key:");
239 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
240 println!("Corresponding Ethereum address: {:x}", eth_address);
241 kp.public.as_bytes().to_vec()
242 }
243 SuiKeyPair::Ed25519(kp) => {
244 println!("Ed25519 key:");
245 kp.public().as_bytes().to_vec()
246 }
247 SuiKeyPair::Secp256r1(kp) => {
248 println!("Secp256r1 key:");
249 kp.public().as_bytes().to_vec()
250 }
251 };
252 println!("Corresponding Sui address: {:?}", sui_address);
253 println!("Corresponding PublicKey: {:?}", Hex::encode(pubkey));
254 Ok(())
255}
256
257pub fn generate_bridge_node_config_and_write_to_file(
259 path: &PathBuf,
260 run_client: bool,
261) -> Result<(), anyhow::Error> {
262 let mut config = BridgeNodeConfig {
263 server_listen_port: 9191,
264 metrics_port: 9184,
265 bridge_authority_key_path: PathBuf::from("/path/to/your/bridge_authority_key"),
266 sui: SuiConfig {
267 sui_rpc_url: "your_sui_rpc_url".to_string(),
268 sui_bridge_chain_id: BridgeChainId::SuiTestnet as u8,
269 bridge_client_key_path: None,
270 bridge_client_gas_object: None,
271 sui_bridge_module_last_processed_event_id_override: None,
272 sui_bridge_next_sequence_number_override: None,
273 },
274 eth: EthConfig {
275 eth_rpc_url: "your_eth_rpc_url".to_string(),
276 eth_bridge_proxy_address: "0x0000000000000000000000000000000000000000".to_string(),
277 eth_bridge_chain_id: BridgeChainId::EthSepolia as u8,
278 eth_contracts_start_block_fallback: Some(0),
279 eth_contracts_start_block_override: None,
280 },
281 approved_governance_actions: vec![],
282 run_client,
283 db_path: None,
284 metrics_key_pair: default_ed25519_key_pair(),
285 metrics: Some(MetricsConfig {
286 push_interval_seconds: None, push_url: "metrics_proxy_url".to_string(),
288 }),
289 watchdog_config: Some(WatchdogConfig {
290 total_supplies: BTreeMap::from_iter(vec![(
291 "eth".to_string(),
292 "0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH"
293 .to_string(),
294 )]),
295 }),
296 };
297 if run_client {
298 config.sui.bridge_client_key_path = Some(PathBuf::from("/path/to/your/bridge_client_key"));
299 config.db_path = Some(PathBuf::from("/path/to/your/client_db"));
300 }
301 config.save(path)
302}
303
304pub async fn publish_and_register_coins_return_add_coins_on_sui_action(
305 wallet_context: &WalletContext,
306 bridge_arg: ObjectArg,
307 token_packages_dir: Vec<PathBuf>,
308 token_ids: Vec<u8>,
309 token_prices: Vec<u64>,
310 nonce: u64,
311) -> BridgeAction {
312 assert!(token_ids.len() == token_packages_dir.len());
313 assert!(token_prices.len() == token_packages_dir.len());
314 let sui_client = wallet_context.get_client().await.unwrap();
315 let quorum_driver_api = Arc::new(sui_client.quorum_driver_api().clone());
316 let rgp = sui_client
317 .governance_api()
318 .get_reference_gas_price()
319 .await
320 .unwrap();
321
322 let senders = wallet_context.get_addresses();
323 assert!(senders.len() >= token_packages_dir.len());
325
326 let mut publish_tokens_tasks = vec![];
328
329 for (token_package_dir, sender) in token_packages_dir.iter().zip(senders.clone()) {
330 let gas = wallet_context
331 .get_one_gas_object_owned_by_address(sender)
332 .await
333 .unwrap()
334 .unwrap();
335 let tx = TestTransactionBuilder::new(sender, gas, rgp)
336 .publish(token_package_dir.to_path_buf())
337 .build();
338 let tx = wallet_context.sign_transaction(&tx).await;
339 let api_clone = quorum_driver_api.clone();
340 publish_tokens_tasks.push(tokio::spawn(async move {
341 api_clone.execute_transaction_block(
342 tx,
343 SuiTransactionBlockResponseOptions::new()
344 .with_effects()
345 .with_input()
346 .with_events()
347 .with_object_changes()
348 .with_balance_changes(),
349 Some(sui_types::transaction_driver_types::ExecuteTransactionRequestType::WaitForLocalExecution),
350 ).await
351 }));
352 }
353 let publish_coin_responses = join_all(publish_tokens_tasks).await;
354
355 let mut token_type_names = vec![];
356 let mut register_tasks = vec![];
357 for (response, sender) in publish_coin_responses.into_iter().zip(senders.clone()) {
358 let response = response.unwrap().unwrap();
359 assert_eq!(
360 response.effects.unwrap().status(),
361 &SuiExecutionStatus::Success
362 );
363 let object_changes = response.object_changes.unwrap();
364 let mut tc = None;
365 let mut type_ = None;
366 let mut uc = None;
367 let mut metadata = None;
368 for object_change in &object_changes {
369 if let o @ sui_json_rpc_types::ObjectChange::Created { object_type, .. } = object_change
370 {
371 if object_type.name.as_str().starts_with("TreasuryCap") {
372 assert!(tc.is_none() && type_.is_none());
373 tc = Some(o.clone());
374 type_ = Some(object_type.type_params.first().unwrap().clone());
375 } else if object_type.name.as_str().starts_with("UpgradeCap") {
376 assert!(uc.is_none());
377 uc = Some(o.clone());
378 } else if object_type.name.as_str().starts_with("CoinMetadata") {
379 assert!(metadata.is_none());
380 metadata = Some(o.clone());
381 }
382 }
383 }
384 let (tc, type_, uc, metadata) =
385 (tc.unwrap(), type_.unwrap(), uc.unwrap(), metadata.unwrap());
386
387 let mut builder = ProgrammableTransactionBuilder::new();
389 let bridge_arg = builder.obj(bridge_arg).unwrap();
390 let uc_arg = builder
391 .obj(ObjectArg::ImmOrOwnedObject(uc.object_ref()))
392 .unwrap();
393 let tc_arg = builder
394 .obj(ObjectArg::ImmOrOwnedObject(tc.object_ref()))
395 .unwrap();
396 let metadata_arg = builder
397 .obj(ObjectArg::ImmOrOwnedObject(metadata.object_ref()))
398 .unwrap();
399 builder.programmable_move_call(
400 BRIDGE_PACKAGE_ID,
401 BRIDGE_MODULE_NAME.into(),
402 BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME.into(),
403 vec![type_.clone()],
404 vec![bridge_arg, tc_arg, uc_arg, metadata_arg],
405 );
406 let pt = builder.finish();
407 let gas = wallet_context
408 .get_one_gas_object_owned_by_address(sender)
409 .await
410 .unwrap()
411 .unwrap();
412 let tx = TransactionData::new_programmable(sender, vec![gas], pt, 1_000_000_000, rgp);
413 let signed_tx = wallet_context.sign_transaction(&tx).await;
414 let api_clone = quorum_driver_api.clone();
415 register_tasks.push(async move {
416 api_clone
417 .execute_transaction_block(
418 signed_tx,
419 SuiTransactionBlockResponseOptions::new().with_effects(),
420 None,
421 )
422 .await
423 });
424 token_type_names.push(type_);
425 }
426 for response in join_all(register_tasks).await {
427 assert_eq!(
428 response.unwrap().effects.unwrap().status(),
429 &SuiExecutionStatus::Success
430 );
431 }
432
433 BridgeAction::AddTokensOnSuiAction(AddTokensOnSuiAction {
434 nonce,
435 chain_id: BridgeChainId::SuiCustom,
436 native: false,
437 token_ids,
438 token_type_names,
439 token_prices,
440 })
441}
442
443pub async fn wait_for_server_to_be_up(server_url: String, timeout_sec: u64) -> anyhow::Result<()> {
444 let now = std::time::Instant::now();
445 loop {
446 if let Ok(true) = reqwest::Client::new()
447 .get(server_url.clone())
448 .header(reqwest::header::ACCEPT, APPLICATION_JSON)
449 .send()
450 .await
451 .map(|res| res.status().is_success())
452 {
453 break;
454 }
455 if now.elapsed().as_secs() > timeout_sec {
456 anyhow::bail!("Server is not up and running after {} seconds", timeout_sec);
457 }
458 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
459 }
460 Ok(())
461}
462
463pub async fn get_committee_voting_power_by_name(
466 bridge_committee: &Arc<BridgeCommittee>,
467 system_state: &SuiSystemStateSummary,
468) -> BTreeMap<String, StakeUnit> {
469 let mut sui_committee: BTreeMap<_, _> = system_state
470 .active_validators
471 .iter()
472 .map(|v| (v.sui_address, v.name.clone()))
473 .collect();
474 bridge_committee
475 .members()
476 .iter()
477 .map(|v| {
478 (
479 sui_committee
480 .remove(&v.1.sui_address)
481 .unwrap_or(v.1.base_url.clone()),
482 v.1.voting_power,
483 )
484 })
485 .collect()
486}
487
488pub async fn get_validator_names_by_pub_keys(
491 bridge_committee: &Arc<BridgeCommittee>,
492 system_state: &SuiSystemStateSummary,
493) -> BTreeMap<BridgeAuthorityPublicKeyBytes, String> {
494 let mut sui_committee: BTreeMap<_, _> = system_state
495 .active_validators
496 .iter()
497 .map(|v| (v.sui_address, v.name.clone()))
498 .collect();
499 bridge_committee
500 .members()
501 .iter()
502 .map(|(name, validator)| {
503 (
504 name.clone(),
505 sui_committee
506 .remove(&validator.sui_address)
507 .unwrap_or(validator.base_url.clone()),
508 )
509 })
510 .collect()
511}