1use std::{path::PathBuf, str::FromStr, sync::Arc};
6
7use anyhow::anyhow;
8use clap::*;
9use ethers::{
10 providers::Middleware,
11 types::{Address as EthAddress, U256},
12};
13use fastcrypto::{
14 encoding::{Encoding, Hex},
15 hash::{HashFunction, Keccak256},
16};
17use iota_bridge::{
18 abi::{EthBridgeCommittee, EthIotaBridge, eth_iota_bridge},
19 crypto::BridgeAuthorityPublicKeyBytes,
20 error::BridgeResult,
21 iota_client::IotaBridgeClient,
22 types::{
23 AddTokensOnEvmAction, AddTokensOnIotaAction, AssetPriceUpdateAction,
24 BlocklistCommitteeAction, BlocklistType, BridgeAction, EmergencyAction,
25 EmergencyActionType, EvmContractUpgradeAction, LimitUpdateAction,
26 },
27 utils::{EthSigner, get_eth_signer_client},
28};
29use iota_config::Config;
30use iota_json_rpc_types::IotaObjectDataOptions;
31use iota_keys::keypair_file::read_key;
32use iota_sdk::IotaClientBuilder;
33use iota_types::{
34 BRIDGE_PACKAGE_ID, TypeTag,
35 base_types::{IotaAddress, ObjectID, ObjectRef},
36 bridge::{BRIDGE_MODULE_NAME, BridgeChainId},
37 crypto::{IotaKeyPair, Signature},
38 programmable_transaction_builder::ProgrammableTransactionBuilder,
39 transaction::{ObjectArg, Transaction, TransactionData},
40};
41use move_core_types::ident_str;
42use serde::{Deserialize, Serialize};
43use serde_with::serde_as;
44use shared_crypto::intent::{Intent, IntentMessage};
45use tracing::info;
46
47pub const SEPOLIA_BRIDGE_PROXY_ADDR: &str = "0xAE68F87938439afEEDd6552B0E83D2CbC2473623";
48
49#[derive(Parser)]
50pub struct Args {
51 #[command(subcommand)]
52 pub command: BridgeCommand,
53}
54
55#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
56pub enum Network {
57 Testnet,
58}
59
60#[derive(Parser)]
61pub enum BridgeCommand {
62 CreateBridgeValidatorKey {
63 path: PathBuf,
64 },
65 CreateBridgeClientKey {
66 path: PathBuf,
67 #[arg(long, default_value = "false")]
68 use_ecdsa: bool,
69 },
70 ExamineKey {
73 path: PathBuf,
74 #[arg(long)]
75 is_validator_key: bool,
76 },
77 CreateBridgeNodeConfigTemplate {
78 path: PathBuf,
79 #[arg(long)]
80 run_client: bool,
81 },
82 Governance {
84 #[arg(long)]
86 config_path: PathBuf,
87 #[arg(long)]
88 chain_id: u8,
89 #[command(subcommand)]
90 cmd: GovernanceClientCommands,
91 #[arg(long)]
93 dry_run: bool,
94 },
95 ViewEthBridge {
97 #[arg(long)]
98 network: Option<Network>,
99 #[arg(long)]
100 bridge_proxy: Option<EthAddress>,
101 #[arg(long)]
102 eth_rpc_url: String,
103 },
104 ViewBridgeRegistration {
106 #[arg(long)]
107 iota_rpc_url: String,
108 },
109 ViewIotaBridge {
111 #[arg(long)]
112 iota_rpc_url: String,
113 #[arg(long, default_value = "false")]
114 hex: bool,
115 #[arg(long, default_value = "false")]
116 ping: bool,
117 },
118 Client {
120 #[arg(long)]
122 config_path: PathBuf,
123 #[command(subcommand)]
124 cmd: BridgeClientCommands,
125 },
126}
127
128#[derive(Parser)]
129pub enum GovernanceClientCommands {
130 EmergencyButton {
131 #[arg(name = "nonce", long)]
132 nonce: u64,
133 #[arg(name = "action-type", long)]
134 action_type: EmergencyActionType,
135 },
136 UpdateCommitteeBlocklist {
137 #[arg(name = "nonce", long)]
138 nonce: u64,
139 #[arg(name = "blocklist-type", long)]
140 blocklist_type: BlocklistType,
141 #[arg(name = "pubkey-hex", use_value_delimiter = true, long)]
142 pubkeys_hex: Vec<BridgeAuthorityPublicKeyBytes>,
143 },
144 UpdateLimit {
145 #[arg(name = "nonce", long)]
146 nonce: u64,
147 #[arg(name = "sending-chain", long)]
148 sending_chain: u8,
149 #[arg(name = "new-usd-limit", long)]
150 new_usd_limit: u64,
151 },
152 UpdateAssetPrice {
153 #[arg(name = "nonce", long)]
154 nonce: u64,
155 #[arg(name = "token-id", long)]
156 token_id: u8,
157 #[arg(name = "new-usd-price", long)]
158 new_usd_price: u64,
159 },
160 AddTokensOnIota {
161 #[arg(name = "nonce", long)]
162 nonce: u64,
163 #[arg(name = "token-ids", use_value_delimiter = true, long)]
164 token_ids: Vec<u8>,
165 #[arg(name = "token-type-names", use_value_delimiter = true, long)]
166 token_type_names: Vec<TypeTag>,
167 #[arg(name = "token-prices", use_value_delimiter = true, long)]
168 token_prices: Vec<u64>,
169 },
170 AddTokensOnEvm {
171 #[arg(name = "nonce", long)]
172 nonce: u64,
173 #[arg(name = "token-ids", use_value_delimiter = true, long)]
174 token_ids: Vec<u8>,
175 #[arg(name = "token-type-names", use_value_delimiter = true, long)]
176 token_addresses: Vec<EthAddress>,
177 #[arg(name = "token-prices", use_value_delimiter = true, long)]
178 token_prices: Vec<u64>,
179 #[arg(name = "token-iota-decimals", use_value_delimiter = true, long)]
180 token_iota_decimals: Vec<u8>,
181 },
182 #[command(name = "upgrade-evm-contract")]
183 UpgradeEVMContract {
184 #[arg(name = "nonce", long)]
185 nonce: u64,
186 #[arg(name = "proxy-address", long)]
187 proxy_address: EthAddress,
188 #[arg(name = "implementation-address", long)]
190 implementation_address: EthAddress,
191 #[arg(name = "function-selector", long)]
193 function_selector: Option<String>,
194 #[arg(name = "params", use_value_delimiter = true, long)]
196 params: Vec<String>,
197 },
198}
199
200pub fn make_action(chain_id: BridgeChainId, cmd: &GovernanceClientCommands) -> BridgeAction {
201 match cmd {
202 GovernanceClientCommands::EmergencyButton { nonce, action_type } => {
203 BridgeAction::EmergencyAction(EmergencyAction {
204 nonce: *nonce,
205 chain_id,
206 action_type: *action_type,
207 })
208 }
209 GovernanceClientCommands::UpdateCommitteeBlocklist {
210 nonce,
211 blocklist_type,
212 pubkeys_hex,
213 } => BridgeAction::BlocklistCommitteeAction(BlocklistCommitteeAction {
214 nonce: *nonce,
215 chain_id,
216 blocklist_type: *blocklist_type,
217 members_to_update: pubkeys_hex.clone(),
218 }),
219 GovernanceClientCommands::UpdateLimit {
220 nonce,
221 sending_chain,
222 new_usd_limit,
223 } => {
224 let sending_chain_id =
225 BridgeChainId::try_from(*sending_chain).expect("Invalid sending chain id");
226 BridgeAction::LimitUpdateAction(LimitUpdateAction {
227 nonce: *nonce,
228 chain_id,
229 sending_chain_id,
230 new_usd_limit: *new_usd_limit,
231 })
232 }
233 GovernanceClientCommands::UpdateAssetPrice {
234 nonce,
235 token_id,
236 new_usd_price,
237 } => BridgeAction::AssetPriceUpdateAction(AssetPriceUpdateAction {
238 nonce: *nonce,
239 chain_id,
240 token_id: *token_id,
241 new_usd_price: *new_usd_price,
242 }),
243 GovernanceClientCommands::AddTokensOnIota {
244 nonce,
245 token_ids,
246 token_type_names,
247 token_prices,
248 } => {
249 assert_eq!(token_ids.len(), token_type_names.len());
250 assert_eq!(token_ids.len(), token_prices.len());
251 BridgeAction::AddTokensOnIotaAction(AddTokensOnIotaAction {
252 nonce: *nonce,
253 chain_id,
254 native: false, token_ids: token_ids.clone(),
256 token_type_names: token_type_names.clone(),
257 token_prices: token_prices.clone(),
258 })
259 }
260 GovernanceClientCommands::AddTokensOnEvm {
261 nonce,
262 token_ids,
263 token_addresses,
264 token_prices,
265 token_iota_decimals,
266 } => {
267 assert_eq!(token_ids.len(), token_addresses.len());
268 assert_eq!(token_ids.len(), token_prices.len());
269 assert_eq!(token_ids.len(), token_iota_decimals.len());
270 BridgeAction::AddTokensOnEvmAction(AddTokensOnEvmAction {
271 nonce: *nonce,
272 native: true, chain_id,
274 token_ids: token_ids.clone(),
275 token_addresses: token_addresses.clone(),
276 token_prices: token_prices.clone(),
277 token_iota_decimals: token_iota_decimals.clone(),
278 })
279 }
280 GovernanceClientCommands::UpgradeEVMContract {
281 nonce,
282 proxy_address,
283 implementation_address,
284 function_selector,
285 params,
286 } => {
287 let call_data = match function_selector {
288 Some(function_selector) => encode_call_data(function_selector, params),
289 None => vec![],
290 };
291 BridgeAction::EvmContractUpgradeAction(EvmContractUpgradeAction {
292 nonce: *nonce,
293 chain_id,
294 proxy_address: *proxy_address,
295 new_impl_address: *implementation_address,
296 call_data,
297 })
298 }
299 }
300}
301
302fn encode_call_data(function_selector: &str, params: &[String]) -> Vec<u8> {
303 let left = function_selector
304 .find('(')
305 .expect("Invalid function selector, no left parentheses");
306 let right = function_selector
307 .find(')')
308 .expect("Invalid function selector, no right parentheses");
309 let param_types = function_selector[left + 1..right]
310 .split(',')
311 .map(|x| x.trim())
312 .collect::<Vec<&str>>();
313
314 assert_eq!(param_types.len(), params.len(), "Invalid number of params");
315
316 let mut call_data = Keccak256::digest(function_selector).digest[0..4].to_vec();
317 let mut tokens = vec![];
318 for (param, param_type) in params.iter().zip(param_types.iter()) {
319 match param_type.to_lowercase().as_str() {
320 "uint256" => {
321 tokens.push(ethers::abi::Token::Uint(
322 ethers::types::U256::from_dec_str(param).expect("Invalid U256"),
323 ));
324 }
325 "bool" => {
326 tokens.push(ethers::abi::Token::Bool(match param.as_str() {
327 "true" => true,
328 "false" => false,
329 _ => panic!("Invalid bool in params"),
330 }));
331 }
332 "string" => {
333 tokens.push(ethers::abi::Token::String(param.clone()));
334 }
335 _ => panic!("Invalid param type"),
337 }
338 }
339 if !tokens.is_empty() {
340 call_data.extend(ethers::abi::encode(&tokens));
341 }
342 call_data
343}
344
345pub fn select_contract_address(
346 config: &LoadedBridgeCliConfig,
347 cmd: &GovernanceClientCommands,
348) -> EthAddress {
349 match cmd {
350 GovernanceClientCommands::EmergencyButton { .. } => config.eth_bridge_proxy_address,
351 GovernanceClientCommands::UpdateCommitteeBlocklist { .. } => {
352 config.eth_bridge_committee_proxy_address
353 }
354 GovernanceClientCommands::UpdateLimit { .. } => config.eth_bridge_limiter_proxy_address,
355 GovernanceClientCommands::UpdateAssetPrice { .. } => config.eth_bridge_config_proxy_address,
356 GovernanceClientCommands::UpgradeEVMContract { proxy_address, .. } => *proxy_address,
357 GovernanceClientCommands::AddTokensOnIota { .. } => unreachable!(),
358 GovernanceClientCommands::AddTokensOnEvm { .. } => config.eth_bridge_config_proxy_address,
359 }
360}
361
362#[serde_as]
363#[derive(Clone, Debug, Deserialize, Serialize)]
364#[serde(rename_all = "kebab-case")]
365pub struct BridgeCliConfig {
366 pub iota_rpc_url: String,
368 pub eth_rpc_url: String,
370 pub eth_bridge_proxy_address: EthAddress,
372 pub iota_key_path: Option<PathBuf>,
380 pub eth_key_path: Option<PathBuf>,
382}
383
384impl Config for BridgeCliConfig {}
385
386pub struct LoadedBridgeCliConfig {
387 pub iota_rpc_url: String,
389 pub eth_rpc_url: String,
391 pub eth_bridge_proxy_address: EthAddress,
393 pub eth_bridge_committee_proxy_address: EthAddress,
395 pub eth_bridge_config_proxy_address: EthAddress,
397 pub eth_bridge_limiter_proxy_address: EthAddress,
399 iota_key: IotaKeyPair,
401 eth_signer: EthSigner,
403}
404
405impl LoadedBridgeCliConfig {
406 pub async fn load(cli_config: BridgeCliConfig) -> anyhow::Result<Self> {
407 if cli_config.eth_key_path.is_none() && cli_config.iota_key_path.is_none() {
408 return Err(anyhow!(
409 "At least one of `iota_key_path` or `eth_key_path` must be provided"
410 ));
411 }
412 let iota_key = if let Some(iota_key_path) = &cli_config.iota_key_path {
413 Some(read_key(iota_key_path, false)?)
414 } else {
415 None
416 };
417 let eth_key = if let Some(eth_key_path) = &cli_config.eth_key_path {
418 let eth_key = read_key(eth_key_path, true)?;
419 Some(eth_key)
420 } else {
421 None
422 };
423 let (eth_key, iota_key) = {
424 if eth_key.is_none() {
425 let iota_key = iota_key.unwrap();
426 if !matches!(iota_key, IotaKeyPair::Secp256k1(_)) {
427 return Err(anyhow!("Eth key must be an ECDSA key"));
428 }
429 (iota_key.copy(), iota_key)
430 } else if iota_key.is_none() {
431 let eth_key = eth_key.unwrap();
432 (eth_key.copy(), eth_key)
433 } else {
434 (eth_key.unwrap(), iota_key.unwrap())
435 }
436 };
437
438 let provider = Arc::new(
439 ethers::prelude::Provider::<ethers::providers::Http>::try_from(&cli_config.eth_rpc_url)
440 .unwrap()
441 .interval(std::time::Duration::from_millis(2000)),
442 );
443 let private_key = Hex::encode(eth_key.to_bytes_no_flag());
444 let eth_signer = get_eth_signer_client(&cli_config.eth_rpc_url, &private_key).await?;
445 let iota_bridge = EthIotaBridge::new(cli_config.eth_bridge_proxy_address, provider.clone());
446 let eth_bridge_committee_proxy_address: EthAddress = iota_bridge.committee().call().await?;
447 let eth_bridge_limiter_proxy_address: EthAddress = iota_bridge.limiter().call().await?;
448 let eth_committee =
449 EthBridgeCommittee::new(eth_bridge_committee_proxy_address, provider.clone());
450 let eth_bridge_committee_proxy_address: EthAddress = iota_bridge.committee().call().await?;
451 let eth_bridge_config_proxy_address: EthAddress = eth_committee.config().call().await?;
452
453 let eth_address = eth_signer.address();
454 let eth_chain_id = provider.get_chainid().await?;
455 let iota_address = IotaAddress::from(&iota_key.public());
456 println!("Using IOTA address: {:?}", iota_address);
457 println!("Using Eth address: {:?}", eth_address);
458 println!("Using Eth chain: {:?}", eth_chain_id);
459
460 Ok(Self {
461 iota_rpc_url: cli_config.iota_rpc_url,
462 eth_rpc_url: cli_config.eth_rpc_url,
463 eth_bridge_proxy_address: cli_config.eth_bridge_proxy_address,
464 eth_bridge_committee_proxy_address,
465 eth_bridge_limiter_proxy_address,
466 eth_bridge_config_proxy_address,
467 iota_key,
468 eth_signer,
469 })
470 }
471}
472
473impl LoadedBridgeCliConfig {
474 pub fn eth_signer(self: &LoadedBridgeCliConfig) -> &EthSigner {
475 &self.eth_signer
476 }
477
478 pub async fn get_iota_account_info(
479 self: &LoadedBridgeCliConfig,
480 ) -> anyhow::Result<(IotaKeyPair, IotaAddress, ObjectRef)> {
481 let pubkey = self.iota_key.public();
482 let iota_client_address = IotaAddress::from(&pubkey);
483 let iota_sdk_client = IotaClientBuilder::default()
484 .build(self.iota_rpc_url.clone())
485 .await?;
486 let gases = iota_sdk_client
487 .coin_read_api()
488 .get_coins(iota_client_address, None, None, None)
489 .await?
490 .data;
491 let gas = gases
493 .into_iter()
494 .find(|coin| coin.balance >= 5_000_000_000)
495 .ok_or(anyhow!(
496 "Did not find gas object with enough balance for {}",
497 iota_client_address
498 ))?;
499 println!("Using Gas object: {}", gas.coin_object_id);
500 Ok((self.iota_key.copy(), iota_client_address, gas.object_ref()))
501 }
502}
503#[derive(Parser)]
504pub enum BridgeClientCommands {
505 DepositNativeEtherOnEth {
506 #[arg(long)]
507 ether_amount: f64,
508 #[arg(long)]
509 target_chain: u8,
510 #[arg(long)]
511 iota_recipient_address: IotaAddress,
512 },
513 DepositOnIota {
514 #[arg(long)]
515 coin_object_id: ObjectID,
516 #[arg(long)]
517 coin_type: String,
518 #[arg(long)]
519 target_chain: u8,
520 #[arg(long)]
521 recipient_address: EthAddress,
522 },
523 ClaimOnEth {
524 #[arg(long)]
525 seq_num: u64,
526 },
527}
528
529impl BridgeClientCommands {
530 pub async fn handle(
531 self,
532 config: &LoadedBridgeCliConfig,
533 iota_bridge_client: IotaBridgeClient,
534 ) -> anyhow::Result<()> {
535 match self {
536 BridgeClientCommands::DepositNativeEtherOnEth {
537 ether_amount,
538 target_chain,
539 iota_recipient_address,
540 } => {
541 let eth_iota_bridge = EthIotaBridge::new(
542 config.eth_bridge_proxy_address,
543 Arc::new(config.eth_signer().clone()),
544 );
545 let int_part = ether_amount.trunc() as u64;
548 let frac_part = ether_amount.fract();
549 let int_wei = U256::from(int_part) * U256::exp10(18);
550 let frac_wei = U256::from((frac_part * 1_000_000_000_000_000_000f64) as u64);
551 let amount = int_wei + frac_wei;
552 let eth_tx = eth_iota_bridge
553 .bridge_eth(iota_recipient_address.to_vec().into(), target_chain)
554 .value(amount);
555 let pending_tx = eth_tx.send().await.unwrap();
556 let tx_receipt = pending_tx.await.unwrap().unwrap();
557 info!(
558 "Deposited {ether_amount} Ethers to {:?} (target chain {target_chain}). Receipt: {:?}",
559 iota_recipient_address, tx_receipt,
560 );
561 Ok(())
562 }
563 BridgeClientCommands::ClaimOnEth { seq_num } => {
564 claim_on_eth(seq_num, config, iota_bridge_client)
565 .await
566 .map_err(|e| anyhow!("{:?}", e))
567 }
568 BridgeClientCommands::DepositOnIota {
569 coin_object_id,
570 coin_type,
571 target_chain,
572 recipient_address,
573 } => {
574 let target_chain = BridgeChainId::try_from(target_chain).expect("Invalid chain id");
575 let coin_type = TypeTag::from_str(&coin_type).expect("Invalid coin type");
576 deposit_on_iota(
577 coin_object_id,
578 coin_type,
579 target_chain,
580 recipient_address,
581 config,
582 iota_bridge_client,
583 )
584 .await
585 }
586 }
587 }
588}
589
590async fn deposit_on_iota(
591 coin_object_id: ObjectID,
592 coin_type: TypeTag,
593 target_chain: BridgeChainId,
594 recipient_address: EthAddress,
595 config: &LoadedBridgeCliConfig,
596 iota_bridge_client: IotaBridgeClient,
597) -> anyhow::Result<()> {
598 let target_chain = target_chain as u8;
599 let iota_client = iota_bridge_client.iota_client();
600 let bridge_object_arg = iota_bridge_client
601 .get_mutable_bridge_object_arg_must_succeed()
602 .await;
603 let rgp = iota_client
604 .governance_api()
605 .get_reference_gas_price()
606 .await
607 .unwrap();
608 let sender = IotaAddress::from(&config.iota_key.public());
609 let gas_obj_ref = iota_client
610 .coin_read_api()
611 .select_coins(sender, None, 1_000_000_000, vec![])
612 .await?
613 .first()
614 .ok_or(anyhow!("No coin found for address {}", sender))?
615 .object_ref();
616 let coin_obj_ref = iota_client
617 .read_api()
618 .get_object_with_options(coin_object_id, IotaObjectDataOptions::default())
619 .await?
620 .data
621 .unwrap()
622 .object_ref();
623
624 let mut builder = ProgrammableTransactionBuilder::new();
625 let arg_target_chain = builder.pure(target_chain).unwrap();
626 let arg_target_address = builder.pure(recipient_address.as_bytes()).unwrap();
627 let arg_token = builder
628 .obj(ObjectArg::ImmOrOwnedObject(coin_obj_ref))
629 .unwrap();
630 let arg_bridge = builder.obj(bridge_object_arg).unwrap();
631
632 builder.programmable_move_call(
633 BRIDGE_PACKAGE_ID,
634 BRIDGE_MODULE_NAME.to_owned(),
635 ident_str!("send_token").to_owned(),
636 vec![coin_type],
637 vec![arg_bridge, arg_target_chain, arg_target_address, arg_token],
638 );
639 let pt = builder.finish();
640 let tx_data =
641 TransactionData::new_programmable(sender, vec![gas_obj_ref], pt, 500_000_000, rgp);
642 let sig = Signature::new_secure(
643 &IntentMessage::new(Intent::iota_transaction(), tx_data.clone()),
644 &config.iota_key,
645 );
646 let signed_tx = Transaction::from_data(tx_data, vec![sig]);
647 let tx_digest = *signed_tx.digest();
648 info!(?tx_digest, "Sending deposit transaction to IOTA.");
649 let resp = iota_bridge_client
650 .execute_transaction_block_with_effects(signed_tx)
651 .await
652 .expect("Failed to execute transaction block");
653 if !resp.status_ok().unwrap() {
654 return Err(anyhow!("Transaction {:?} failed: {:?}", tx_digest, resp));
655 }
656 let events = resp.events.unwrap();
657 info!(
658 ?tx_digest,
659 "Deposit transaction succeeded. Events: {:?}", events
660 );
661 Ok(())
662}
663
664async fn claim_on_eth(
665 seq_num: u64,
666 config: &LoadedBridgeCliConfig,
667 iota_bridge_client: IotaBridgeClient,
668) -> BridgeResult<()> {
669 let iota_chain_id = iota_bridge_client.get_bridge_summary().await?.chain_id;
670 let parsed_message = iota_bridge_client
671 .get_parsed_token_transfer_message(iota_chain_id, seq_num)
672 .await?;
673 if parsed_message.is_none() {
674 println!("No record found for seq_num: {seq_num}, chain id: {iota_chain_id}");
675 return Ok(());
676 }
677 let parsed_message = parsed_message.unwrap();
678 let sigs = iota_bridge_client
679 .get_token_transfer_action_onchain_signatures_until_success(iota_chain_id, seq_num)
680 .await;
681 if sigs.is_none() {
682 println!("No signatures found for seq_num: {seq_num}, chain id: {iota_chain_id}");
683 return Ok(());
684 }
685 let signatures = sigs
686 .unwrap()
687 .into_iter()
688 .map(|sig: Vec<u8>| ethers::types::Bytes::from(sig))
689 .collect::<Vec<_>>();
690
691 let eth_iota_bridge = EthIotaBridge::new(
692 config.eth_bridge_proxy_address,
693 Arc::new(config.eth_signer().clone()),
694 );
695 let message = eth_iota_bridge::Message::from(parsed_message);
696 let tx = eth_iota_bridge.transfer_bridged_tokens_with_signatures(signatures, message);
697 let _eth_claim_tx_receipt = tx.send().await.unwrap().await.unwrap().unwrap();
698 info!("IOTA to Eth bridge transfer claimed");
699 Ok(())
700}
701
702#[cfg(test)]
703mod tests {
704 use ethers::abi::FunctionExt;
705
706 use super::*;
707
708 #[tokio::test]
709 #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
710 async fn test_encode_call_data() {
711 let abi_json =
712 std::fs::read_to_string("../iota-bridge/abi/tests/mock_iota_bridge_v2.json").unwrap();
713 let abi: ethers::abi::Abi = serde_json::from_str(&abi_json).unwrap();
714
715 let function_selector = "initializeV2Params(uint256,bool,string)";
716 let params = vec!["420".to_string(), "false".to_string(), "hello".to_string()];
717 let call_data = encode_call_data(function_selector, ¶ms);
718
719 let function = abi
720 .functions()
721 .find(|f| {
722 let selector = f.selector();
723 call_data.starts_with(selector.as_ref())
724 })
725 .expect("Function not found");
726
727 let tokens = function.decode_input(&call_data[4..]).unwrap();
729 assert_eq!(
730 tokens,
731 vec![
732 ethers::abi::Token::Uint(ethers::types::U256::from_dec_str("420").unwrap()),
733 ethers::abi::Token::Bool(false),
734 ethers::abi::Token::String("hello".to_string())
735 ]
736 )
737 }
738}