1use std::str::FromStr;
94
95use alloy::primitives::{Address, U256, utils::parse_units};
96use clap::{Parser, builder::RangedU64ValueParser};
97use hopr_bindings::{
98 hoprnetworkregistry::HoprNetworkRegistry, hoprnodesaferegistry::HoprNodeSafeRegistry,
99 hoprnodestakefactory::HoprNodeStakeFactory, hoprtoken::HoprToken,
100};
101use hopr_crypto_types::keypairs::Keypair;
102use tracing::{info, warn};
103
104use crate::{
105 environment_config::NetworkProviderArgs,
106 key_pair::{ArgEnvReader, IdentityFileArgs, ManagerPrivateKeyArgs, PrivateKeyArgs},
107 methods::{
108 SafeSingleton, debug_node_safe_module_setup_main, debug_node_safe_module_setup_on_balance_and_registries,
109 deploy_safe_module_with_targets_and_nodes, deregister_nodes_from_node_safe_registry_and_remove_from_module,
110 include_nodes_to_module, migrate_nodes, register_safes_and_nodes_on_network_registry, transfer_native_tokens,
111 transfer_or_mint_tokens,
112 },
113 utils::{Cmd, HelperErrors},
114};
115
116#[derive(Clone, Debug, Parser)]
118pub enum SafeModuleSubcommands {
119 #[command(visible_alias = "cr")]
121 Create {
122 #[command(flatten)]
124 network_provider: NetworkProviderArgs,
125
126 #[command(flatten)]
128 local_identity: IdentityFileArgs,
129
130 #[clap(
132 help = "Comma separated node Ethereum addresses",
133 long,
134 short = 'o',
135 default_value = None
136 )]
137 node_address: Option<String>,
138
139 #[clap(
141 help = "Comma separated node Ethereum addresses",
142 long,
143 short = 'a',
144 default_value = None
145 )]
146 admin_address: Option<String>,
147
148 #[clap(
150 help = "Threshold for the generated safe, e.g. 1",
151 long,
152 short,
153 value_parser = RangedU64ValueParser::<u32>::new().range(1..),
154 default_value_t = 1
155 )]
156 threshold: u32,
157
158 #[clap(
160 help = "Provide the allowance of the channel contract to manage HOPR tokens on behalf of deployed safe. Value in ether, e.g. 10",
161 long,
162 short = 'l',
163 value_parser = clap::value_parser!(f64),
164 )]
165 allowance: Option<f64>,
166
167 #[clap(
169 help = "Hopr amount in ether, e.g. 10",
170 long,
171 short = 'm',
172 value_parser = clap::value_parser!(f64),
173 )]
174 hopr_amount: Option<f64>,
175
176 #[clap(
178 help = "Native token amount in ether, e.g. 1",
179 long,
180 short = 'g',
181 value_parser = clap::value_parser!(f64),
182 )]
183 native_amount: Option<f64>,
184
185 #[command(flatten)]
188 private_key: PrivateKeyArgs,
189
190 #[command(flatten, name = "manager_private_key")]
193 manager_private_key: ManagerPrivateKeyArgs,
194 },
195
196 #[command(visible_alias = "mg")]
198 Migrate {
199 #[command(flatten)]
201 network_provider: NetworkProviderArgs,
202
203 #[command(flatten)]
205 local_identity: IdentityFileArgs,
206
207 #[clap(
209 help = "Comma separated node Ethereum addresses",
210 long,
211 short = 'o',
212 default_value = None
213 )]
214 node_address: Option<String>,
215
216 #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
218 safe_address: String,
219
220 #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
222 module_address: String,
223
224 #[clap(
226 help = "Provide the allowance of the channel contract to manage HOPR tokens on behalf of deployed safe. Value in ether, e.g. 10",
227 long,
228 short = 'l',
229 value_parser = clap::value_parser!(f64),
230 )]
231 allowance: Option<f64>,
232
233 #[command(flatten)]
236 private_key: PrivateKeyArgs,
237
238 #[command(flatten, name = "manager_private_key")]
241 manager_private_key: ManagerPrivateKeyArgs,
242 },
243
244 #[command(visible_alias = "mv")]
246 Move {
247 #[command(flatten)]
249 network_provider: NetworkProviderArgs,
250
251 #[command(flatten)]
253 local_identity: IdentityFileArgs,
254
255 #[clap(
257 help = "Comma separated node Ethereum addresses",
258 long,
259 short = 'o',
260 default_value = None
261 )]
262 node_address: Option<String>,
263
264 #[clap(help = "Comma separated old module addresses", long, short = 'u')]
266 old_module_address: String,
267
268 #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
270 new_safe_address: String,
271
272 #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
274 new_module_address: String,
275
276 #[command(flatten)]
279 private_key: PrivateKeyArgs,
280
281 #[command(flatten, name = "manager_private_key")]
284 manager_private_key: ManagerPrivateKeyArgs,
285 },
286
287 #[command(visible_alias = "dg")]
289 Debug {
290 #[command(flatten)]
292 network_provider: NetworkProviderArgs,
293
294 #[command(flatten)]
296 local_identity: IdentityFileArgs,
297
298 #[clap(
300 help = "Comma separated node Ethereum addresses",
301 long,
302 short = 'o',
303 default_value = None
304 )]
305 node_address: Option<String>,
306
307 #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
309 safe_address: String,
310
311 #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
313 module_address: String,
314 },
315}
316
317impl SafeModuleSubcommands {
318 #[allow(clippy::too_many_arguments)]
327 pub async fn execute_safe_module_creation(
328 network_provider: NetworkProviderArgs,
329 local_identity: IdentityFileArgs,
330 node_address: Option<String>,
331 admin_address: Option<String>,
332 threshold: u32,
333 allowance: Option<f64>,
334 hopr_amount: Option<f64>,
335 native_amount: Option<f64>,
336 private_key: PrivateKeyArgs,
337 manager_private_key: ManagerPrivateKeyArgs,
338 ) -> Result<(), HelperErrors> {
339 let mut node_eth_addresses: Vec<Address> = Vec::new();
341 if let Some(addresses) = node_address {
342 node_eth_addresses.extend(
343 addresses
344 .split(',')
345 .map(|addr| {
346 Address::from_str(addr)
347 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
348 })
349 .collect::<Result<Vec<_>, _>>()?,
350 );
351 }
352 node_eth_addresses.extend(
354 local_identity
355 .to_addresses()
356 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
357 .into_iter()
358 .map(Address::from),
359 );
360
361 let node_addresses = if node_eth_addresses.is_empty() {
362 None
363 } else {
364 Some(node_eth_addresses.clone())
365 };
366
367 let token_allowance: U256 = match allowance {
369 Some(allw) => parse_units(&allw.to_string(), "ether")
370 .map_err(|_| HelperErrors::ParseError("Failed to parse allowance units".into()))?
371 .into(),
372 None => U256::MAX,
373 };
374
375 let signer_private_key = private_key.read_default()?;
377 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
379 let contract_addresses = network_provider.get_network_details_from_name()?;
380
381 let admin_eth_addresses: Vec<Address> = match admin_address {
383 Some(admin_address_str) => admin_address_str
384 .split(',')
385 .map(|addr| Address::from_str(addr).unwrap())
386 .collect(),
387 None => vec![signer_private_key.clone().public().to_address().into()],
388 };
389
390 let hopr_stake_factory = HoprNodeStakeFactory::new(
398 contract_addresses.addresses.node_stake_v2_factory.into(),
399 rpc_provider.clone(),
400 );
401
402 let (safe, node_module) = deploy_safe_module_with_targets_and_nodes(
403 hopr_stake_factory,
404 contract_addresses.addresses.token.into(),
405 contract_addresses.addresses.channels.into(),
406 contract_addresses.addresses.module_implementation.into(),
407 contract_addresses.addresses.announcements.into(),
408 token_allowance,
409 node_addresses,
410 admin_eth_addresses,
411 U256::from(threshold),
412 )
413 .await?;
414
415 println!("safe {:?}", safe.address());
416 println!("node_module {:?}", node_module.address());
417
418 if let Some(hopr_amount_for_safe) = hopr_amount {
420 let hopr_token = HoprToken::new(contract_addresses.addresses.token.into(), rpc_provider.clone());
421 let hopr_to_be_transferred: U256 = parse_units(&hopr_amount_for_safe.to_string(), "ether")
422 .map_err(|_| HelperErrors::ParseError("Failed to parse HOPR amount units".into()))?
423 .into();
424
425 transfer_or_mint_tokens(hopr_token, vec![*safe.address()], vec![hopr_to_be_transferred]).await?;
426 info!(
427 "safe {:?} has received {:?} HOPR tokens",
428 safe.address(),
429 hopr_amount_for_safe
430 );
431 }
432
433 if let Some(native_amount_for_node) = native_amount {
435 let native_to_be_transferred: U256 = parse_units(&native_amount_for_node.to_string(), "ether")
436 .map_err(|_| HelperErrors::ParseError("Failed to parse HOPR amount units".into()))?
437 .into();
438 let native_amounts = vec![native_to_be_transferred; node_eth_addresses.len()];
439 transfer_native_tokens(rpc_provider.clone(), node_eth_addresses.clone(), native_amounts).await?;
440 info!(
441 "each node in {:?} has received {:?} native tokens",
442 node_eth_addresses, native_amount_for_node
443 );
444 }
445
446 if node_eth_addresses.is_empty() {
448 info!("No nodes provided. Skip actions around network registry");
449 return Ok(());
450 }
451
452 if let Ok(manager_private_key) = manager_private_key.read_default() {
454 let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
456 let hopr_network_registry = HoprNetworkRegistry::new(
457 contract_addresses.addresses.network_registry.into(),
458 manager_rpc_provider.clone(),
459 );
460 let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
463 hopr_network_registry,
464 vec![*safe.address(); node_eth_addresses.len()],
465 node_eth_addresses.clone(),
466 )
467 .await?;
468 info!(
469 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
470 removed_pairs_num, added_pairs_num
471 );
472 } else {
473 info!("skipping inclusion to the network registry as no manager private key has been provided.");
474 }
475
476 Ok(())
477 }
478
479 #[allow(clippy::too_many_arguments)]
483 pub async fn execute_safe_module_moving(
484 network_provider: NetworkProviderArgs,
485 local_identity: IdentityFileArgs,
486 node_address: Option<String>,
487 old_module_address: String,
488 new_safe_address: String,
489 new_module_address: String,
490 private_key: PrivateKeyArgs,
491 manager_private_key: ManagerPrivateKeyArgs,
492 ) -> Result<(), HelperErrors> {
493 let mut node_eth_addresses: Vec<Address> = Vec::new();
495 if let Some(addresses) = node_address {
496 node_eth_addresses.extend(
497 addresses
498 .split(',')
499 .map(|addr| {
500 Address::from_str(addr)
501 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
502 })
503 .collect::<Result<Vec<_>, _>>()?,
504 );
505 }
506 node_eth_addresses.extend(
508 local_identity
509 .to_addresses()
510 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
511 .into_iter()
512 .map(Address::from),
513 );
514
515 let safe_addr = Address::from_str(&new_safe_address)
517 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", new_safe_address)))?;
518 let module_addr = Address::from_str(&new_module_address).map_err(|_| {
519 HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", new_module_address))
520 })?;
521 let old_module_addr: Vec<Address> = old_module_address
522 .split(',')
523 .map(|addr| Address::from_str(addr).unwrap())
524 .collect();
525
526 let signer_private_key = private_key.read_default()?;
528 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
530 let contract_addresses = network_provider.get_network_details_from_name()?;
531
532 let hopr_node_safe_registry = HoprNodeSafeRegistry::new(
538 contract_addresses.addresses.node_safe_registry.into(),
539 rpc_provider.clone(),
540 );
541 let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
542
543 if !node_eth_addresses.is_empty() {
544 match deregister_nodes_from_node_safe_registry_and_remove_from_module(
546 hopr_node_safe_registry.clone(),
547 node_eth_addresses.clone(),
548 old_module_addr,
549 signer_private_key.clone(),
550 )
551 .await
552 {
553 Ok(_) => {
554 info!("Nodes are deregistered from old safes");
555 }
556 Err(e) => {
557 return Err(e);
558 }
559 };
560
561 match include_nodes_to_module(
563 safe.clone(),
564 node_eth_addresses.clone(),
565 module_addr,
566 signer_private_key,
567 )
568 .await
569 {
570 Ok(_) => {
571 info!("Nodes are included to the new module");
572 }
573 Err(e) => {
574 return Err(e);
575 }
576 };
577 };
578
579 if node_eth_addresses.is_empty() {
581 info!("No nodes provided. Skip actions around network registry");
582 return Ok(());
583 }
584
585 if let Ok(manager_private_key) = manager_private_key.read_default() {
587 let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
589 let hopr_network_registry = HoprNetworkRegistry::new(
590 contract_addresses.addresses.network_registry.into(),
591 manager_rpc_provider.clone(),
592 );
593 let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
596 hopr_network_registry,
597 vec![*safe.address(); node_eth_addresses.len()],
598 node_eth_addresses.clone(),
599 )
600 .await?;
601 info!(
602 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
603 removed_pairs_num, added_pairs_num
604 );
605 } else {
606 info!("skipping inclusion to the network registry as no manager private key has been provided.");
607 }
608
609 Ok(())
610 }
611
612 #[allow(clippy::too_many_arguments)]
616 pub async fn execute_safe_module_migration(
617 network_provider: NetworkProviderArgs,
618 local_identity: IdentityFileArgs,
619 node_address: Option<String>,
620 safe_address: String,
621 module_address: String,
622 allowance: Option<f64>,
623 private_key: PrivateKeyArgs,
624 manager_private_key: ManagerPrivateKeyArgs,
625 ) -> Result<(), HelperErrors> {
626 let mut node_eth_addresses: Vec<Address> = Vec::new();
628 if let Some(addresses) = node_address {
629 node_eth_addresses.extend(
630 addresses
631 .split(',')
632 .map(|addr| {
633 Address::from_str(addr)
634 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
635 })
636 .collect::<Result<Vec<_>, _>>()?,
637 );
638 }
639 node_eth_addresses.extend(
641 local_identity
642 .to_addresses()
643 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
644 .into_iter()
645 .map(Address::from),
646 );
647
648 let token_allowance: U256 = match allowance {
650 Some(allw) => parse_units(&allw.to_string(), "ether")
651 .map_err(|_| HelperErrors::ParseError("Failed to parse allowance units".into()))?
652 .into(),
653 None => U256::MAX,
654 };
655
656 let safe_addr = Address::from_str(&safe_address)
658 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", safe_address)))?;
659 let module_addr = Address::from_str(&module_address)
660 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", module_address)))?;
661
662 let signer_private_key = private_key.read_default()?;
664 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
666 let contract_addresses = network_provider.get_network_details_from_name()?;
667
668 let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
669
670 migrate_nodes(
675 safe.clone(),
676 module_addr,
677 contract_addresses.addresses.channels.into(),
678 contract_addresses.addresses.token.into(),
679 contract_addresses.addresses.announcements.into(),
680 token_allowance,
681 signer_private_key,
682 )
683 .await?;
684 info!("a new network has been included due to the migration");
685
686 if node_eth_addresses.is_empty() {
688 info!("No nodes provided. Skip actions around network registry");
689 return Ok(());
690 }
691
692 if let Ok(manager_private_key) = manager_private_key.read_default() {
694 let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
696 let hopr_network_registry = HoprNetworkRegistry::new(
697 contract_addresses.addresses.network_registry.into(),
698 manager_rpc_provider.clone(),
699 );
700 let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
703 hopr_network_registry,
704 vec![*safe.address(); node_eth_addresses.len()],
705 node_eth_addresses.clone(),
706 )
707 .await?;
708 info!(
709 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
710 removed_pairs_num, added_pairs_num
711 );
712 } else {
713 info!("skipping inclusion to the network registry as no manager private key has been provided.");
714 }
715 Ok(())
716 }
717
718 #[allow(clippy::too_many_arguments)]
729 pub async fn execute_safe_module_debugging(
730 network_provider: NetworkProviderArgs,
731 local_identity: IdentityFileArgs,
732 node_address: Option<String>,
733 safe_address: String,
734 module_address: String,
735 ) -> Result<(), HelperErrors> {
736 info!("Reading all the node addresses...");
738 let mut node_eth_addresses: Vec<Address> = Vec::new();
739 if let Some(addresses) = node_address {
740 node_eth_addresses.extend(
741 addresses
742 .split(',')
743 .map(|addr| {
744 Address::from_str(addr)
745 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
746 })
747 .collect::<Result<Vec<_>, _>>()?,
748 );
749 }
750 node_eth_addresses.extend(
752 local_identity
753 .to_addresses()
754 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
755 .into_iter()
756 .map(Address::from),
757 );
758
759 let safe_addr = Address::from_str(&safe_address)
761 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", safe_address)))?;
762 let module_addr = Address::from_str(&module_address)
763 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", module_address)))?;
764
765 let rpc_provider = network_provider.get_provider_without_signer().await?;
767 let contract_addresses = network_provider.get_network_details_from_name()?;
768
769 let hopr_token = HoprToken::new(contract_addresses.addresses.token.into(), rpc_provider.clone());
770 let network_registry = HoprNetworkRegistry::new(
771 contract_addresses.addresses.network_registry.into(),
772 rpc_provider.clone(),
773 );
774 let node_safe_registry = HoprNodeSafeRegistry::new(
775 contract_addresses.addresses.node_safe_registry.into(),
776 rpc_provider.clone(),
777 );
778
779 for node in node_eth_addresses {
781 info!("Starting debug checks for node: {:?}", node);
782 info!("Checking node registration with network registry...");
783 let registered_safe = debug_node_safe_module_setup_on_balance_and_registries(
784 network_registry.clone(),
785 node_safe_registry.clone(),
786 &node,
787 )
788 .await
789 .map_err(HelperErrors::MulticallError)?;
790
791 if registered_safe != safe_addr {
793 warn!(
794 "Node {:?} is not registered with the provided safe {:?}",
795 node, safe_addr
796 );
797 }
798 info!("Checking node and safe association in node-safe registry...");
799 debug_node_safe_module_setup_main(
800 hopr_token.clone(),
801 &module_addr,
802 &node,
803 &safe_addr,
804 &contract_addresses.addresses.channels.into(),
805 &contract_addresses.addresses.announcements.into(),
806 )
807 .await
808 .map_err(HelperErrors::MulticallError)?;
809 }
810 Ok(())
811 }
812}
813
814impl Cmd for SafeModuleSubcommands {
815 fn run(self) -> Result<(), HelperErrors> {
817 Ok(())
819 }
820
821 async fn async_run(self) -> Result<(), HelperErrors> {
822 match self {
823 SafeModuleSubcommands::Create {
824 network_provider,
825 local_identity,
826 node_address,
827 admin_address,
828 threshold,
829 allowance,
830 hopr_amount,
831 native_amount,
832 private_key,
833 manager_private_key,
834 } => {
835 SafeModuleSubcommands::execute_safe_module_creation(
836 network_provider,
837 local_identity,
838 node_address,
839 admin_address,
840 threshold,
841 allowance,
842 hopr_amount,
843 native_amount,
844 private_key,
845 manager_private_key,
846 )
847 .await
848 }
849 SafeModuleSubcommands::Move {
850 network_provider,
851 local_identity,
852 node_address,
853 old_module_address,
854 new_safe_address,
855 new_module_address,
856 private_key,
857 manager_private_key,
858 } => {
859 SafeModuleSubcommands::execute_safe_module_moving(
860 network_provider,
861 local_identity,
862 node_address,
863 old_module_address,
864 new_safe_address,
865 new_module_address,
866 private_key,
867 manager_private_key,
868 )
869 .await
870 }
871 SafeModuleSubcommands::Migrate {
872 network_provider,
873 local_identity,
874 node_address,
875 safe_address,
876 module_address,
877 allowance,
878 private_key,
879 manager_private_key,
880 } => {
881 SafeModuleSubcommands::execute_safe_module_migration(
882 network_provider,
883 local_identity,
884 node_address,
885 safe_address,
886 module_address,
887 allowance,
888 private_key,
889 manager_private_key,
890 )
891 .await
892 }
893 SafeModuleSubcommands::Debug {
894 network_provider,
895 local_identity,
896 node_address,
897 safe_address,
898 module_address,
899 } => {
900 SafeModuleSubcommands::execute_safe_module_debugging(
901 network_provider,
902 local_identity,
903 node_address,
904 safe_address,
905 module_address,
906 )
907 .await
908 }
909 }
910 }
911}