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).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        // read private key
527        let signer_private_key = private_key.read_default()?;
528        // get RPC provider for the given network and environment
529        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        // 1. Deregister the old node-safe from node-safe registry
533        // 2. Remove nodes from the old module
534        // 3. Include node to the new module
535        // 4. Remove node from network registry
536        // 5. Include node to network registry
537        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            // first deregister nodes from their old safe
545            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            // then include nodes to module
562            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        // action around network registry
580        if node_eth_addresses.is_empty() {
581            info!("No nodes provided. Skip actions around network registry");
582            return Ok(());
583        }
584
585        // read private key of network registry manager
586        if let Ok(manager_private_key) = manager_private_key.read_default() {
587            // get RPC provider for the given network and environment
588            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            // Overwrite any past registration of provided nodes in the network registry.
594            // This action is the same as calling `hopli network-registry manager-register`
595            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    /// Execute the command, which migrates nodes to a new network
613    /// Note that it does not register the node with the new safe on NodeSafeRegistry,
614    /// because it is an action that nodes need to do on-start.
615    #[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        // read all the node addresses
627        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        // if local identity dirs/path is provided, read addresses from identity files
640        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        // get allowance
649        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        // parse safe and module addresses
657        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        // read private key
663        let signer_private_key = private_key.read_default()?;
664        // get RPC provider for the given network and environment
665        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        // Create a Safe tx to Multisend contract,
671        // 1. scope the Channel contract of the new network to the module as target and set default permissions.
672        // 2. scope the Announcement contract as target to the module
673        // 3. approve HOPR tokens of the Safe proxy to be transferred by the new Channels contract
674        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        // action around network registry
687        if node_eth_addresses.is_empty() {
688            info!("No nodes provided. Skip actions around network registry");
689            return Ok(());
690        }
691
692        // read private key of network registry manager
693        if let Ok(manager_private_key) = manager_private_key.read_default() {
694            // get RPC provider for the given network and environment
695            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            // Overwrite any past registration of provided nodes in the network registry.
701            // This action is the same as calling `hopli network-registry manager-register`
702            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    /// Execute the command to debug the following:
719    /// 1. node xDAI balance
720    /// 2. If node has been included on Network Registry
721    /// 3. If node and safe are associated on Node Safe Registry
722    /// 4. If Safe is owned by the correct owner(s)
723    /// 5. Safe’s wxHOPR balance and allowance
724    /// 6. if node is included in the module
725    /// 7. If the channel contract is included as a target
726    /// 8. If the announce contract is included as a target
727    /// 9. If safe is the owner of the module
728    #[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        // read all the node addresses
737        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        // if local identity dirs/path is provided, read addresses from identity files
751        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        // parse safe and module addresses
760        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        // get RPC provider for the given network and environment
766        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        // loop through all the nodes and debug
780        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            // compare the registered safe with the provided safe
792            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    /// Run the execute_safe_module_creation function
816    fn run(self) -> Result<(), HelperErrors> {
817        // self.execute_safe_module_creation()
818        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}