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