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)
519 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {new_module_address:?}")))?;
520 let old_module_addr: Vec<Address> = old_module_address
521 .split(',')
522 .map(|addr| Address::from_str(addr).unwrap())
523 .collect();
524
525 let signer_private_key = private_key.read_default()?;
527 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
529 let contract_addresses = network_provider.get_network_details_from_name()?;
530
531 let hopr_node_safe_registry = HoprNodeSafeRegistry::new(
537 contract_addresses.addresses.node_safe_registry.into(),
538 rpc_provider.clone(),
539 );
540 let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
541
542 if !node_eth_addresses.is_empty() {
543 match deregister_nodes_from_node_safe_registry_and_remove_from_module(
545 hopr_node_safe_registry.clone(),
546 node_eth_addresses.clone(),
547 old_module_addr,
548 signer_private_key.clone(),
549 )
550 .await
551 {
552 Ok(_) => {
553 info!("Nodes are deregistered from old safes");
554 }
555 Err(e) => {
556 return Err(e);
557 }
558 };
559
560 match include_nodes_to_module(
562 safe.clone(),
563 node_eth_addresses.clone(),
564 module_addr,
565 signer_private_key,
566 )
567 .await
568 {
569 Ok(_) => {
570 info!("Nodes are included to the new module");
571 }
572 Err(e) => {
573 return Err(e);
574 }
575 };
576 };
577
578 if node_eth_addresses.is_empty() {
580 info!("No nodes provided. Skip actions around network registry");
581 return Ok(());
582 }
583
584 if let Ok(manager_private_key) = manager_private_key.read_default() {
586 let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
588 let hopr_network_registry = HoprNetworkRegistry::new(
589 contract_addresses.addresses.network_registry.into(),
590 manager_rpc_provider.clone(),
591 );
592 let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
595 hopr_network_registry,
596 vec![*safe.address(); node_eth_addresses.len()],
597 node_eth_addresses.clone(),
598 )
599 .await?;
600 info!(
601 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
602 removed_pairs_num, added_pairs_num
603 );
604 } else {
605 info!("skipping inclusion to the network registry as no manager private key has been provided.");
606 }
607
608 Ok(())
609 }
610
611 #[allow(clippy::too_many_arguments)]
615 pub async fn execute_safe_module_migration(
616 network_provider: NetworkProviderArgs,
617 local_identity: IdentityFileArgs,
618 node_address: Option<String>,
619 safe_address: String,
620 module_address: String,
621 allowance: Option<f64>,
622 private_key: PrivateKeyArgs,
623 manager_private_key: ManagerPrivateKeyArgs,
624 ) -> Result<(), HelperErrors> {
625 let mut node_eth_addresses: Vec<Address> = Vec::new();
627 if let Some(addresses) = node_address {
628 node_eth_addresses.extend(
629 addresses
630 .split(',')
631 .map(|addr| {
632 Address::from_str(addr)
633 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {e:?}")))
634 })
635 .collect::<Result<Vec<_>, _>>()?,
636 );
637 }
638 node_eth_addresses.extend(
640 local_identity
641 .to_addresses()
642 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {e:?}")))?
643 .into_iter()
644 .map(Address::from),
645 );
646
647 let token_allowance: U256 = match allowance {
649 Some(allw) => parse_units(&allw.to_string(), "ether")
650 .map_err(|_| HelperErrors::ParseError("Failed to parse allowance units".into()))?
651 .into(),
652 None => U256::MAX,
653 };
654
655 let safe_addr = Address::from_str(&safe_address)
657 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {safe_address:?}")))?;
658 let module_addr = Address::from_str(&module_address)
659 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {module_address:?}")))?;
660
661 let signer_private_key = private_key.read_default()?;
663 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
665 let contract_addresses = network_provider.get_network_details_from_name()?;
666
667 let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
668
669 migrate_nodes(
674 safe.clone(),
675 module_addr,
676 contract_addresses.addresses.channels.into(),
677 contract_addresses.addresses.token.into(),
678 contract_addresses.addresses.announcements.into(),
679 token_allowance,
680 signer_private_key,
681 )
682 .await?;
683 info!("a new network has been included due to the migration");
684
685 if node_eth_addresses.is_empty() {
687 info!("No nodes provided. Skip actions around network registry");
688 return Ok(());
689 }
690
691 if let Ok(manager_private_key) = manager_private_key.read_default() {
693 let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
695 let hopr_network_registry = HoprNetworkRegistry::new(
696 contract_addresses.addresses.network_registry.into(),
697 manager_rpc_provider.clone(),
698 );
699 let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
702 hopr_network_registry,
703 vec![*safe.address(); node_eth_addresses.len()],
704 node_eth_addresses.clone(),
705 )
706 .await?;
707 info!(
708 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
709 removed_pairs_num, added_pairs_num
710 );
711 } else {
712 info!("skipping inclusion to the network registry as no manager private key has been provided.");
713 }
714 Ok(())
715 }
716
717 #[allow(clippy::too_many_arguments)]
728 pub async fn execute_safe_module_debugging(
729 network_provider: NetworkProviderArgs,
730 local_identity: IdentityFileArgs,
731 node_address: Option<String>,
732 safe_address: String,
733 module_address: String,
734 ) -> Result<(), HelperErrors> {
735 info!("Reading all the node addresses...");
737 let mut node_eth_addresses: Vec<Address> = Vec::new();
738 if let Some(addresses) = node_address {
739 node_eth_addresses.extend(
740 addresses
741 .split(',')
742 .map(|addr| {
743 Address::from_str(addr)
744 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {e:?}")))
745 })
746 .collect::<Result<Vec<_>, _>>()?,
747 );
748 }
749 node_eth_addresses.extend(
751 local_identity
752 .to_addresses()
753 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {e:?}")))?
754 .into_iter()
755 .map(Address::from),
756 );
757
758 let safe_addr = Address::from_str(&safe_address)
760 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {safe_address:?}")))?;
761 let module_addr = Address::from_str(&module_address)
762 .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {module_address:?}")))?;
763
764 let rpc_provider = network_provider.get_provider_without_signer().await?;
766 let contract_addresses = network_provider.get_network_details_from_name()?;
767
768 let hopr_token = HoprToken::new(contract_addresses.addresses.token.into(), rpc_provider.clone());
769 let network_registry = HoprNetworkRegistry::new(
770 contract_addresses.addresses.network_registry.into(),
771 rpc_provider.clone(),
772 );
773 let node_safe_registry = HoprNodeSafeRegistry::new(
774 contract_addresses.addresses.node_safe_registry.into(),
775 rpc_provider.clone(),
776 );
777
778 for node in node_eth_addresses {
780 info!("Starting debug checks for node: {:?}", node);
781 info!("Checking node registration with network registry...");
782 let registered_safe = debug_node_safe_module_setup_on_balance_and_registries(
783 network_registry.clone(),
784 node_safe_registry.clone(),
785 &node,
786 )
787 .await
788 .map_err(HelperErrors::MulticallError)?;
789
790 if registered_safe != safe_addr {
792 warn!(
793 "Node {:?} is not registered with the provided safe {:?}",
794 node, safe_addr
795 );
796 }
797 info!("Checking node and safe association in node-safe registry...");
798 debug_node_safe_module_setup_main(
799 hopr_token.clone(),
800 &module_addr,
801 &node,
802 &safe_addr,
803 &contract_addresses.addresses.channels.into(),
804 &contract_addresses.addresses.announcements.into(),
805 )
806 .await
807 .map_err(HelperErrors::MulticallError)?;
808 }
809 Ok(())
810 }
811}
812
813impl Cmd for SafeModuleSubcommands {
814 fn run(self) -> Result<(), HelperErrors> {
816 Ok(())
818 }
819
820 async fn async_run(self) -> Result<(), HelperErrors> {
821 match self {
822 SafeModuleSubcommands::Create {
823 network_provider,
824 local_identity,
825 node_address,
826 admin_address,
827 threshold,
828 allowance,
829 hopr_amount,
830 native_amount,
831 private_key,
832 manager_private_key,
833 } => {
834 SafeModuleSubcommands::execute_safe_module_creation(
835 network_provider,
836 local_identity,
837 node_address,
838 admin_address,
839 threshold,
840 allowance,
841 hopr_amount,
842 native_amount,
843 private_key,
844 manager_private_key,
845 )
846 .await
847 }
848 SafeModuleSubcommands::Move {
849 network_provider,
850 local_identity,
851 node_address,
852 old_module_address,
853 new_safe_address,
854 new_module_address,
855 private_key,
856 manager_private_key,
857 } => {
858 SafeModuleSubcommands::execute_safe_module_moving(
859 network_provider,
860 local_identity,
861 node_address,
862 old_module_address,
863 new_safe_address,
864 new_module_address,
865 private_key,
866 manager_private_key,
867 )
868 .await
869 }
870 SafeModuleSubcommands::Migrate {
871 network_provider,
872 local_identity,
873 node_address,
874 safe_address,
875 module_address,
876 allowance,
877 private_key,
878 manager_private_key,
879 } => {
880 SafeModuleSubcommands::execute_safe_module_migration(
881 network_provider,
882 local_identity,
883 node_address,
884 safe_address,
885 module_address,
886 allowance,
887 private_key,
888 manager_private_key,
889 )
890 .await
891 }
892 SafeModuleSubcommands::Debug {
893 network_provider,
894 local_identity,
895 node_address,
896 safe_address,
897 module_address,
898 } => {
899 SafeModuleSubcommands::execute_safe_module_debugging(
900 network_provider,
901 local_identity,
902 node_address,
903 safe_address,
904 module_address,
905 )
906 .await
907 }
908 }
909 }
910}