1use std::{path::PathBuf, str::FromStr, sync::Arc};
6
7use anyhow::anyhow;
8use ethers::{
9 core::k256::ecdsa::SigningKey,
10 middleware::SignerMiddleware,
11 prelude::*,
12 providers::{Http, Provider},
13 signers::Wallet,
14 types::Address as EthAddress,
15};
16use fastcrypto::{
17 ed25519::Ed25519KeyPair,
18 encoding::{Encoding, Hex},
19 secp256k1::Secp256k1KeyPair,
20 traits::{EncodeDecodeBase64, KeyPair},
21};
22use futures::future::join_all;
23use iota_config::Config;
24use iota_json_rpc_types::{
25 IotaExecutionStatus, IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponseOptions,
26};
27use iota_keys::keypair_file::read_key;
28use iota_sdk::wallet_context::WalletContext;
29use iota_test_transaction_builder::TestTransactionBuilder;
30use iota_types::{
31 BRIDGE_PACKAGE_ID,
32 base_types::IotaAddress,
33 bridge::{BRIDGE_MODULE_NAME, BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME, BridgeChainId},
34 crypto::{IotaKeyPair, ToFromBytes, get_key_pair},
35 programmable_transaction_builder::ProgrammableTransactionBuilder,
36 transaction::{ObjectArg, TransactionData},
37};
38
39use crate::{
40 abi::{EthBridgeCommittee, EthBridgeConfig, EthBridgeLimiter, EthBridgeVault, EthIotaBridge},
41 config::{BridgeNodeConfig, EthConfig, IotaConfig},
42 crypto::{BridgeAuthorityKeyPair, BridgeAuthorityPublicKeyBytes},
43 server::APPLICATION_JSON,
44 types::{AddTokensOnIotaAction, BridgeAction},
45};
46
47pub type EthSigner = SignerMiddleware<Provider<Http>, Wallet<SigningKey>>;
48
49pub struct EthBridgeContracts<P> {
50 pub bridge: EthIotaBridge<Provider<P>>,
51 pub committee: EthBridgeCommittee<Provider<P>>,
52 pub limiter: EthBridgeLimiter<Provider<P>>,
53 pub vault: EthBridgeVault<Provider<P>>,
54 pub config: EthBridgeConfig<Provider<P>>,
55}
56
57pub fn generate_bridge_authority_key_and_write_to_file(
60 path: &PathBuf,
61) -> Result<(), anyhow::Error> {
62 let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
63 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
64 println!(
65 "Corresponding Ethereum address by this ecdsa key: {:?}",
66 eth_address
67 );
68 let iota_address = IotaAddress::from(&kp.public);
69 println!(
70 "Corresponding IOTA address by this ecdsa key: {:?}",
71 iota_address
72 );
73 let base64_encoded = kp.encode_base64();
74 std::fs::write(path, base64_encoded)
75 .map_err(|err| anyhow!("Failed to write encoded key to path: {:?}", err))
76}
77
78pub fn generate_bridge_client_key_and_write_to_file(
81 path: &PathBuf,
82 use_ecdsa: bool,
83) -> Result<(), anyhow::Error> {
84 let kp = if use_ecdsa {
85 let (_, kp): (_, Secp256k1KeyPair) = get_key_pair();
86 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
87 println!(
88 "Corresponding Ethereum address by this ecdsa key: {:?}",
89 eth_address
90 );
91 IotaKeyPair::from(kp)
92 } else {
93 let (_, kp): (_, Ed25519KeyPair) = get_key_pair();
94 IotaKeyPair::from(kp)
95 };
96 let iota_address = IotaAddress::from(&kp.public());
97 println!("Corresponding IOTA address by this key: {:?}", iota_address);
98
99 let contents = kp.encode_base64();
100 std::fs::write(path, contents)
101 .map_err(|err| anyhow!("Failed to write encoded key to path: {:?}", err))
102}
103
104pub async fn get_eth_contract_addresses<P: ethers::providers::JsonRpcClient + 'static>(
107 bridge_proxy_address: EthAddress,
108 provider: &Arc<Provider<P>>,
109) -> anyhow::Result<(EthAddress, EthAddress, EthAddress, EthAddress)> {
110 let iota_bridge = EthIotaBridge::new(bridge_proxy_address, provider.clone());
111 let committee_address: EthAddress = iota_bridge.committee().call().await?;
112 let limiter_address: EthAddress = iota_bridge.limiter().call().await?;
113 let vault_address: EthAddress = iota_bridge.vault().call().await?;
114 let committee = EthBridgeCommittee::new(committee_address, provider.clone());
115 let config_address: EthAddress = committee.config().call().await?;
116
117 Ok((
118 committee_address,
119 limiter_address,
120 vault_address,
121 config_address,
122 ))
123}
124
125pub async fn get_eth_contracts<P: ethers::providers::JsonRpcClient + 'static>(
128 bridge_proxy_address: EthAddress,
129 provider: &Arc<Provider<P>>,
130) -> anyhow::Result<EthBridgeContracts<P>> {
131 let iota_bridge = EthIotaBridge::new(bridge_proxy_address, provider.clone());
132 let committee_address: EthAddress = iota_bridge.committee().call().await?;
133 let limiter_address: EthAddress = iota_bridge.limiter().call().await?;
134 let vault_address: EthAddress = iota_bridge.vault().call().await?;
135 let committee = EthBridgeCommittee::new(committee_address, provider.clone());
136 let config_address: EthAddress = committee.config().call().await?;
137
138 let limiter = EthBridgeLimiter::new(limiter_address, provider.clone());
139 let vault = EthBridgeVault::new(vault_address, provider.clone());
140 let config = EthBridgeConfig::new(config_address, provider.clone());
141 Ok(EthBridgeContracts {
142 bridge: iota_bridge,
143 committee,
144 limiter,
145 vault,
146 config,
147 })
148}
149
150pub fn examine_key(path: &PathBuf, is_validator_key: bool) -> Result<(), anyhow::Error> {
153 let key = read_key(path, is_validator_key)?;
154 let iota_address = IotaAddress::from(&key.public());
155 let pubkey = match key {
156 IotaKeyPair::Secp256k1(kp) => {
157 println!("Secp256k1 key:");
158 let eth_address = BridgeAuthorityPublicKeyBytes::from(&kp.public).to_eth_address();
159 println!("Corresponding Ethereum address: {:x}", eth_address);
160 kp.public.as_bytes().to_vec()
161 }
162 IotaKeyPair::Ed25519(kp) => {
163 println!("Ed25519 key:");
164 kp.public().as_bytes().to_vec()
165 }
166 IotaKeyPair::Secp256r1(kp) => {
167 println!("Secp256r1 key:");
168 kp.public().as_bytes().to_vec()
169 }
170 };
171 println!("Corresponding IOTA address: {:?}", iota_address);
172 println!("Corresponding PublicKey: {:?}", Hex::encode(pubkey));
173 Ok(())
174}
175
176pub fn generate_bridge_node_config_and_write_to_file(
178 path: &PathBuf,
179 run_client: bool,
180) -> Result<(), anyhow::Error> {
181 let mut config = BridgeNodeConfig {
182 server_listen_port: 9191,
183 metrics_port: 9184,
184 bridge_authority_key_path: PathBuf::from("/path/to/your/bridge_authority_key"),
185 iota: IotaConfig {
186 iota_rpc_url: "your_iota_rpc_url".to_string(),
187 iota_bridge_chain_id: BridgeChainId::IotaTestnet as u8,
188 bridge_client_key_path: None,
189 bridge_client_gas_object: None,
190 iota_bridge_module_last_processed_event_id_override: None,
191 },
192 eth: EthConfig {
193 eth_rpc_url: "your_eth_rpc_url".to_string(),
194 eth_bridge_proxy_address: "0x0000000000000000000000000000000000000000".to_string(),
195 eth_bridge_chain_id: BridgeChainId::EthSepolia as u8,
196 eth_contracts_start_block_fallback: Some(0),
197 eth_contracts_start_block_override: None,
198 },
199 approved_governance_actions: vec![],
200 run_client,
201 db_path: None,
202 };
203 if run_client {
204 config.iota.bridge_client_key_path = Some(PathBuf::from("/path/to/your/bridge_client_key"));
205 config.db_path = Some(PathBuf::from("/path/to/your/client_db"));
206 }
207 config.save(path)
208}
209
210pub async fn get_eth_signer_client(url: &str, private_key_hex: &str) -> anyhow::Result<EthSigner> {
211 let provider = Provider::<Http>::try_from(url)
212 .unwrap()
213 .interval(std::time::Duration::from_millis(2000));
214 let chain_id = provider.get_chainid().await?;
215 let wallet = Wallet::from_str(private_key_hex)
216 .unwrap()
217 .with_chain_id(chain_id.as_u64());
218 Ok(SignerMiddleware::new(provider, wallet))
219}
220
221pub async fn publish_and_register_coins_return_add_coins_on_iota_action(
222 wallet_context: &WalletContext,
223 bridge_arg: ObjectArg,
224 token_packages_dir: Vec<PathBuf>,
225 token_ids: Vec<u8>,
226 token_prices: Vec<u64>,
227 nonce: u64,
228) -> BridgeAction {
229 assert!(token_ids.len() == token_packages_dir.len());
230 assert!(token_prices.len() == token_packages_dir.len());
231 let iota_client = wallet_context.get_client().await.unwrap();
232 let quorum_driver_api = Arc::new(iota_client.quorum_driver_api().clone());
233 let rgp = iota_client
234 .governance_api()
235 .get_reference_gas_price()
236 .await
237 .unwrap();
238
239 let senders = wallet_context.get_addresses();
240 assert!(senders.len() >= token_packages_dir.len());
242
243 let mut publish_tokens_tasks = vec![];
245
246 for (token_package_dir, sender) in token_packages_dir.iter().zip(senders.clone()) {
247 let gas = wallet_context
248 .get_one_gas_object_owned_by_address(sender)
249 .await
250 .unwrap()
251 .unwrap();
252 let tx = TestTransactionBuilder::new(sender, gas, rgp)
253 .publish(token_package_dir.to_path_buf())
254 .build();
255 let tx = wallet_context.sign_transaction(&tx);
256 let api_clone = quorum_driver_api.clone();
257 publish_tokens_tasks.push(tokio::spawn(async move {
258 api_clone.execute_transaction_block(
259 tx,
260 IotaTransactionBlockResponseOptions::new()
261 .with_effects()
262 .with_input()
263 .with_events()
264 .with_object_changes()
265 .with_balance_changes(),
266 Some(iota_types::quorum_driver_types::ExecuteTransactionRequestType::WaitForLocalExecution),
267 ).await
268 }));
269 }
270 let publish_coin_responses = join_all(publish_tokens_tasks).await;
271
272 let mut token_type_names = vec![];
273 let mut register_tasks = vec![];
274 for (response, sender) in publish_coin_responses.into_iter().zip(senders.clone()) {
275 let response = response.unwrap().unwrap();
276 assert_eq!(
277 response.effects.unwrap().status(),
278 &IotaExecutionStatus::Success
279 );
280 let object_changes = response.object_changes.unwrap();
281 let mut tc = None;
282 let mut type_ = None;
283 let mut uc = None;
284 let mut metadata = None;
285 for object_change in &object_changes {
286 if let o @ iota_json_rpc_types::ObjectChange::Created { object_type, .. } =
287 object_change
288 {
289 if object_type.name.as_str().starts_with("TreasuryCap") {
290 assert!(tc.is_none() && type_.is_none());
291 tc = Some(o.clone());
292 type_ = Some(object_type.type_params.first().unwrap().clone());
293 } else if object_type.name.as_str().starts_with("UpgradeCap") {
294 assert!(uc.is_none());
295 uc = Some(o.clone());
296 } else if object_type.name.as_str().starts_with("CoinMetadata") {
297 assert!(metadata.is_none());
298 metadata = Some(o.clone());
299 }
300 }
301 }
302 let (tc, type_, uc, metadata) =
303 (tc.unwrap(), type_.unwrap(), uc.unwrap(), metadata.unwrap());
304
305 let mut builder = ProgrammableTransactionBuilder::new();
307 let bridge_arg = builder.obj(bridge_arg).unwrap();
308 let uc_arg = builder
309 .obj(ObjectArg::ImmOrOwnedObject(uc.object_ref()))
310 .unwrap();
311 let tc_arg = builder
312 .obj(ObjectArg::ImmOrOwnedObject(tc.object_ref()))
313 .unwrap();
314 let metadata_arg = builder
315 .obj(ObjectArg::ImmOrOwnedObject(metadata.object_ref()))
316 .unwrap();
317 builder.programmable_move_call(
318 BRIDGE_PACKAGE_ID,
319 BRIDGE_MODULE_NAME.into(),
320 BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME.into(),
321 vec![type_.clone()],
322 vec![bridge_arg, tc_arg, uc_arg, metadata_arg],
323 );
324 let pt = builder.finish();
325 let gas = wallet_context
326 .get_one_gas_object_owned_by_address(sender)
327 .await
328 .unwrap()
329 .unwrap();
330 let tx = TransactionData::new_programmable(sender, vec![gas], pt, 1_000_000_000, rgp);
331 let signed_tx = wallet_context.sign_transaction(&tx);
332 let api_clone = quorum_driver_api.clone();
333 register_tasks.push(async move {
334 api_clone
335 .execute_transaction_block(
336 signed_tx,
337 IotaTransactionBlockResponseOptions::new().with_effects(),
338 None,
339 )
340 .await
341 });
342 token_type_names.push(type_);
343 }
344 for response in join_all(register_tasks).await {
345 assert_eq!(
346 response.unwrap().effects.unwrap().status(),
347 &IotaExecutionStatus::Success
348 );
349 }
350
351 BridgeAction::AddTokensOnIotaAction(AddTokensOnIotaAction {
352 nonce,
353 chain_id: BridgeChainId::IotaCustom,
354 native: false,
355 token_ids,
356 token_type_names,
357 token_prices,
358 })
359}
360
361pub async fn wait_for_server_to_be_up(server_url: String, timeout_sec: u64) -> anyhow::Result<()> {
362 let now = std::time::Instant::now();
363 loop {
364 if let Ok(true) = reqwest::Client::new()
365 .get(server_url.clone())
366 .header(reqwest::header::ACCEPT, APPLICATION_JSON)
367 .send()
368 .await
369 .map(|res| res.status().is_success())
370 {
371 break;
372 }
373 if now.elapsed().as_secs() > timeout_sec {
374 anyhow::bail!("Server is not up and running after {} seconds", timeout_sec);
375 }
376 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
377 }
378 Ok(())
379}