hopli/
safe_module.rs

1//! This module contains arguments and functions to manage safe and module.
2//! [SafeModuleSubcommands] defines three subcommands: create, move, and migrate.
3//! - [SafeModuleSubcommands::Create] creates staking wallets (safe and node management module) and execute necessary
4//!   on-chain transactions to setup a HOPR node. Detailed breakdown of the steps:
5//!     - create a Safe proxy instance and HOPR node management module proxy instance
6//!     - include nodes configure default permissions on the created module proxy
7//!     - fund the node and Safe with some native tokens and HOPR tokens respectively
8//!     - approve HOPR tokens to be transferred from the Safe proxy instaces by Channels contract
9//!     - Use manager wallet to add nodes and staking safes to the Network Registry contract
10//! - [SafeModuleSubcommands::Move] moves a node from to an existing Safe. Note that the Safe should has a node
11//!   management module attached and configured. Note that the admin key of the old and new safes are the same. This
12//!   command does not support moving nodes to safes controled by a different admin key. Note that all the safes
13//!   involved (old and new) should have a threshold of 1 Detailed breakdown of the steps:
14//!     - use old safes to deregister nodes from Node-safe registry
15//!     - use the new safe to include nodes to the module
16//!     - use manager wallet to deregister nodes from the network registry
17//!     - use manager wallet to register nodes with new safes to the network regsitry
18//! - [SafeModuleSubcommands::Migrate] migrates a node to a different network. It performs the following steps:
19//!     - add the Channel contract of the new network to the module as target and set default permissions.
20//!     - add the Announcement contract as target to the module
21//!     - approve HOPR tokens of the Safe proxy to be transferred by the new Channels contract
22//!     - Use the manager wallet to add nodes and Safes to the Network Registry contract of the new network.
23//! - [SafeModuleSubcommands::Debug] goes through a series of checks to debug the setup of a node and safe.
24//!
25//! It checks the following:
26//!     - node xDAI balance
27//!     - If node has been included on Network Registry
28//!     - If node and safe are associated on Node Safe Registry
29//!     - If Safe is owned by the correct owner(s)
30//!     - Safe’s wxHOPR balance and allowance
31//!     - if the module is enabled
32//!     - if node is included in the module
33//!     - Get all the targets of the safe (then check if channel and announcement are there)
34//!     - Get the owner of the module
35//!
36//! You need to enable the INFO level of the tracing logger to see the output of the debug command.
37//!
38//! Some sample commands
39//! - Express creation of a safe and a module
40//! ```text
41//! hopli safe-module create \
42//!     --network anvil-localhost \
43//!     --contracts-root "../ethereum/contracts" \
44//!     --identity-directory "./test" \
45//!     --password-path "./test/pwd" \
46//!     --admin-address 0x47f2710069F01672D01095cA252018eBf08bF85e,0x0D07Eb66Deb54D48D004765E13DcC028cf56592b \
47//!     --allowance 10.5 \
48//!     --hopr-amount 10 \
49//!     --native-amount 0.1 \
50//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
51//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
52//!     --provider-url "http://localhost:8545"
53//! ```
54//!
55//! - Migrate nodes and safe to a new network
56//! ```text
57//! hopli safe-module migrate \
58//!     --network anvil-localhost2 \
59//!     --contracts-root "../ethereum/contracts" \
60//!     --identity-directory "./test" \
61//!     --password-path "./test/pwd" \
62//!     --safe-address 0x6a64fe01c3aba5bdcd04b81fef375369ca47326f \
63//!     --module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
64//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
65//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
66//!     --provider-url "http://localhost:8545"
67//! ```
68//!
69//! - Move registered nodes to a different set of safe and module
70//! ```text
71//! hopli safe-module move \
72//!     --network anvil-localhost \
73//!     --contracts-root "../ethereum/contracts"  \
74//!     --old-module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
75//!     --new-safe-address 0xce66d19a86600f3c6eb61edd6c431ded5cc92b21 \
76//!     --new-module-address 0x3086c20265cf742b169b05cd0eae1941455e4e9f \
77//!     --node-address 0x93a50B0fFF7b4ED36A3C6445e280E72AC2AEFc51,0x58033D3074D001a32bF379801eaf8969817fFfCf,0xeEDaab91158928647a9270Fe290897eBB1230250 \
78//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
79//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
80//!     --provider-url "http://localhost:8545"
81//!
82//! - Debug nodes and safe
83//! ```text
84//! hopli safe-module debug \
85//!     --network anvil-localhost2 \
86//!     --contracts-root "../ethereum/contracts" \
87//!     --identity-directory "./test" \
88//!     --password-path "./test/pwd" \
89//!     --safe-address 0x6a64fe01c3aba5bdcd04b81fef375369ca47326f \
90//!     --module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
91//!     --provider-url "http://localhost:8545"
92//! ```
93use 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/// CLI arguments for `hopli safe-module`
117#[derive(Clone, Debug, Parser)]
118pub enum SafeModuleSubcommands {
119    /// Create safe and module proxy if nothing exists
120    #[command(visible_alias = "cr")]
121    Create {
122        /// Network name, contracts config file root, and customized provider, if available
123        #[command(flatten)]
124        network_provider: NetworkProviderArgs,
125
126        /// Arguments to locate identity file(s) of HOPR node(s)
127        #[command(flatten)]
128        local_identity: IdentityFileArgs,
129
130        /// node addresses
131        #[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        /// admin addresses
140        #[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        /// Threshold for the generated safe
149        #[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        /// Allowance of the channel contract to manage HOPR tokens on behalf of deployed safe
159        #[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        /// The amount of HOPR tokens (in floating number) to be funded to the new safe
168        #[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        /// The amount of native tokens (in floating number) to be funded per node
177        #[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        /// Access to the private key, of which the wallet either contains sufficient assets
186        /// as the source of funds or it can mint necessary tokens
187        #[command(flatten)]
188        private_key: PrivateKeyArgs,
189
190        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
191        /// If provided, this wallet will grant the created safe access to the network registry
192        #[command(flatten, name = "manager_private_key")]
193        manager_private_key: ManagerPrivateKeyArgs,
194    },
195
196    /// Migrate safe and module to a new network
197    #[command(visible_alias = "mg")]
198    Migrate {
199        /// Network name, contracts config file root, and customized provider, if available
200        #[command(flatten)]
201        network_provider: NetworkProviderArgs,
202
203        /// Arguments to locate identity file(s) of HOPR node(s)
204        #[command(flatten)]
205        local_identity: IdentityFileArgs,
206
207        /// node addresses
208        #[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        /// safe address that the nodes move to
217        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
218        safe_address: String,
219
220        /// module address that the nodes move to
221        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
222        module_address: String,
223
224        /// Allowance of the channel contract to manage HOPR tokens on behalf of deployed safe
225        #[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        /// Access to the private key, of which the wallet either contains sufficient assets
234        /// as the source of funds or it can mint necessary tokens
235        #[command(flatten)]
236        private_key: PrivateKeyArgs,
237
238        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
239        /// If provided, this wallet will grant the created safe access to the network registry
240        #[command(flatten, name = "manager_private_key")]
241        manager_private_key: ManagerPrivateKeyArgs,
242    },
243
244    /// Move nodes to one single safe and module pair
245    #[command(visible_alias = "mv")]
246    Move {
247        /// Network name, contracts config file root, and customized provider, if available
248        #[command(flatten)]
249        network_provider: NetworkProviderArgs,
250
251        /// Arguments to locate identity file(s) of HOPR node(s)
252        #[command(flatten)]
253        local_identity: IdentityFileArgs,
254
255        /// node addresses
256        #[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        /// old module addresses
265        #[clap(help = "Comma separated old module addresses", long, short = 'u')]
266        old_module_address: String,
267
268        /// safe address that the nodes move to
269        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
270        new_safe_address: String,
271
272        /// module address that the nodes move to
273        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
274        new_module_address: String,
275
276        /// Access to the private key, of which the wallet either contains sufficient assets
277        /// as the source of funds or it can mint necessary tokens
278        #[command(flatten)]
279        private_key: PrivateKeyArgs,
280
281        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
282        /// If provided, this wallet will grant the created safe access to the network registry
283        #[command(flatten, name = "manager_private_key")]
284        manager_private_key: ManagerPrivateKeyArgs,
285    },
286
287    /// Debug safe and module setup
288    #[command(visible_alias = "dg")]
289    Debug {
290        /// Network name, contracts config file root, and customized provider, if available
291        #[command(flatten)]
292        network_provider: NetworkProviderArgs,
293
294        /// Arguments to locate identity file(s) of HOPR node(s)
295        #[command(flatten)]
296        local_identity: IdentityFileArgs,
297
298        /// node addresses
299        #[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        /// safe address that the nodes move to
308        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
309        safe_address: String,
310
311        /// module address that the nodes move to
312        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
313        module_address: String,
314    },
315}
316
317impl SafeModuleSubcommands {
318    /// Execute the command, which quickly create necessary staking wallets
319    /// and execute necessary on-chain transactions to setup a HOPR node.
320    ///
321    /// 1. Create a safe instance and a node management module instance:
322    /// 2. Set default permissions for the module
323    /// 3. Include node as a member with restricted permission on sending assets
324    /// 4. transfer some HOPR token to the new safe (directly)
325    /// 5. transfer some native tokens to nodes
326    #[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        // read all the node addresses
340        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        // if local identity dirs/path is provided, read addresses from identity files
353        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        // get allowance
368        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        // read private key
376        let signer_private_key = private_key.read_default()?;
377        // get RPC provider for the given network and environment
378        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        // read all the admin addresses
382        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        // within one multicall, as an owner of the safe
391        // deploy a safe proxy instance and a module proxy instance with multicall as an owner
392        // add announcement as a permitted target in the deployed module proxy
393        // approve token transfer to be done for the safe by channel contracts
394        // if node addresses are known, include nodes to the module by safe
395        // transfer safe ownership to actual admins
396        // set desired threshold
397        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        // direct transfer of some HOPR tokens to the safe
419        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        // distribute some native tokens to the nodes
434        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        // action around network registry
447        if node_eth_addresses.is_empty() {
448            info!("No nodes provided. Skip actions around network registry");
449            return Ok(());
450        }
451
452        // read private key
453        if let Ok(manager_private_key) = manager_private_key.read_default() {
454            // get RPC provider for the given network and environment
455            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            // Overwrite any past registration of provided nodes in the network registry.
461            // This action is the same as calling `hopli network-registry manager-register`
462            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    /// Execute the command, which moves nodes to a new managing safe and module pair
480    /// Note that it does not register the node with the new safe on NodeSafeRegistry,
481    /// because it is an action that nodes need to do on-start.
482    #[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        // read all the node addresses
494        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        // if local identity dirs/path is provided, read addresses from identity files
507        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        // parse safe and module addresses
516        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        // read private key
526        let signer_private_key = private_key.read_default()?;
527        // get RPC provider for the given network and environment
528        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        // 1. Deregister the old node-safe from node-safe registry
532        // 2. Remove nodes from the old module
533        // 3. Include node to the new module
534        // 4. Remove node from network registry
535        // 5. Include node to network registry
536        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            // first deregister nodes from their old safe
544            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            // then include nodes to module
561            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        // action around network registry
579        if node_eth_addresses.is_empty() {
580            info!("No nodes provided. Skip actions around network registry");
581            return Ok(());
582        }
583
584        // read private key of network registry manager
585        if let Ok(manager_private_key) = manager_private_key.read_default() {
586            // get RPC provider for the given network and environment
587            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            // Overwrite any past registration of provided nodes in the network registry.
593            // This action is the same as calling `hopli network-registry manager-register`
594            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    /// Execute the command, which migrates nodes to a new network
612    /// Note that it does not register the node with the new safe on NodeSafeRegistry,
613    /// because it is an action that nodes need to do on-start.
614    #[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        // read all the node addresses
626        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        // if local identity dirs/path is provided, read addresses from identity files
639        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        // get allowance
648        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        // parse safe and module addresses
656        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        // read private key
662        let signer_private_key = private_key.read_default()?;
663        // get RPC provider for the given network and environment
664        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        // Create a Safe tx to Multisend contract,
670        // 1. scope the Channel contract of the new network to the module as target and set default permissions.
671        // 2. scope the Announcement contract as target to the module
672        // 3. approve HOPR tokens of the Safe proxy to be transferred by the new Channels contract
673        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        // action around network registry
686        if node_eth_addresses.is_empty() {
687            info!("No nodes provided. Skip actions around network registry");
688            return Ok(());
689        }
690
691        // read private key of network registry manager
692        if let Ok(manager_private_key) = manager_private_key.read_default() {
693            // get RPC provider for the given network and environment
694            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            // Overwrite any past registration of provided nodes in the network registry.
700            // This action is the same as calling `hopli network-registry manager-register`
701            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    /// Execute the command to debug the following:
718    /// 1. node xDAI balance
719    /// 2. If node has been included on Network Registry
720    /// 3. If node and safe are associated on Node Safe Registry
721    /// 4. If Safe is owned by the correct owner(s)
722    /// 5. Safe’s wxHOPR balance and allowance
723    /// 6. if node is included in the module
724    /// 7. If the channel contract is included as a target
725    /// 8. If the announce contract is included as a target
726    /// 9. If safe is the owner of the module
727    #[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        // read all the node addresses
736        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        // if local identity dirs/path is provided, read addresses from identity files
750        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        // parse safe and module addresses
759        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        // get RPC provider for the given network and environment
765        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        // loop through all the nodes and debug
779        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            // compare the registered safe with the provided safe
791            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    /// Run the execute_safe_module_creation function
815    fn run(self) -> Result<(), HelperErrors> {
816        // self.execute_safe_module_creation()
817        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}