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)
4//!   and execute necessary on-chain transactions to setup a HOPR node.
5//!   Detailed breakdown of the steps:
6//!     - create a Safe proxy instance and HOPR node management module proxy instance
7//!     - include nodes configure default permissions on the created module proxy
8//!     - fund the node and Safe with some native tokens and HOPR tokens respectively
9//!     - approve HOPR tokens to be transferred from the Safe proxy instaces by Channels contract
10//!     - Use manager wallet to add nodes and staking safes to the Network Registry contract
11//! - [SafeModuleSubcommands::Move] moves a node from to an existing Safe.
12//!   Note that the Safe should has a node management module attached and configured.
13//!   Note that the admin key of the old and new safes are the same. This command does not support
14//!   moving nodes to safes controled by a different admin key.
15//!   Note that all the safes involved (old and new) should have a threshold of 1
16//!   Detailed breakdown of the steps:
17//!     - use old safes to deregister nodes from Node-safe registry
18//!     - use the new safe to include nodes to the module
19//!     - use manager wallet to deregister nodes from the network registry
20//!     - use manager wallet to register nodes with new safes to the network regsitry
21//! - [SafeModuleSubcommands::Migrate] migrates a node to a different network.
22//!   It performs the following steps:
23//!     - add the Channel contract of the new network to the module as target and set default permissions.
24//!     - add the Announcement contract as target to the module
25//!     - approve HOPR tokens of the Safe proxy to be transferred by the new Channels contract
26//!     - Use the manager wallet to add nodes and Safes to the Network Registry contract of the new network.
27//! - [SafeModuleSubcommands::Debug] goes through a series of checks to debug the setup of a node and safe.
28//!
29//! It checks the following:
30//!     - node xDAI balance
31//!     - If node has been included on Network Registry
32//!     - If node and safe are associated on Node Safe Registry
33//!     - If Safe is owned by the correct owner(s)
34//!     - Safe’s wxHOPR balance and allowance
35//!     - if the module is enabled
36//!     - if node is included in the module
37//!     - Get all the targets of the safe (then check if channel and announcement are there)
38//!     - Get the owner of the module
39//!
40//! You need to enable the INFO level of the tracing logger to see the output of the debug command.
41//!
42//! Some sample commands
43//! - Express creation of a safe and a module
44//! ```text
45//! hopli safe-module create \
46//!     --network anvil-localhost \
47//!     --contracts-root "../ethereum/contracts" \
48//!     --identity-directory "./test" \
49//!     --password-path "./test/pwd" \
50//!     --admin-address 0x47f2710069F01672D01095cA252018eBf08bF85e,0x0D07Eb66Deb54D48D004765E13DcC028cf56592b \
51//!     --allowance 10.5 \
52//!     --hopr-amount 10 \
53//!     --native-amount 0.1 \
54//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
55//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
56//!     --provider-url "http://localhost:8545"
57//!```
58//!
59//! - Migrate nodes and safe to a new network
60//! ```text
61//! hopli safe-module migrate \
62//!     --network anvil-localhost2 \
63//!     --contracts-root "../ethereum/contracts" \
64//!     --identity-directory "./test" \
65//!     --password-path "./test/pwd" \
66//!     --safe-address 0x6a64fe01c3aba5bdcd04b81fef375369ca47326f \
67//!     --module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
68//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
69//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
70//!     --provider-url "http://localhost:8545"
71//! ```
72//!
73//! - Move registered nodes to a different set of safe and module
74//! ```text
75//! hopli safe-module move \
76//!     --network anvil-localhost \
77//!     --contracts-root "../ethereum/contracts"  \
78//!     --old-module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
79//!     --new-safe-address 0xce66d19a86600f3c6eb61edd6c431ded5cc92b21 \
80//!     --new-module-address 0x3086c20265cf742b169b05cd0eae1941455e4e9f \
81//!     --node-address 0x93a50B0fFF7b4ED36A3C6445e280E72AC2AEFc51,0x58033D3074D001a32bF379801eaf8969817fFfCf,0xeEDaab91158928647a9270Fe290897eBB1230250 \
82//!     --manager-private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
83//!     --private-key 59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
84//!     --provider-url "http://localhost:8545"
85//!
86//! - Debug nodes and safe
87//! ```text
88//! hopli safe-module debug \
89//!     --network anvil-localhost2 \
90//!     --contracts-root "../ethereum/contracts" \
91//!     --identity-directory "./test" \
92//!     --password-path "./test/pwd" \
93//!     --safe-address 0x6a64fe01c3aba5bdcd04b81fef375369ca47326f \
94//!     --module-address 0x5d46d0c5279fd85ce7365e4d668f415685922839 \
95//!     --provider-url "http://localhost:8545"
96//! ```
97use crate::key_pair::{ArgEnvReader, ManagerPrivateKeyArgs};
98use crate::methods::{debug_node_safe_module_setup_main, debug_node_safe_module_setup_on_balance_and_registries};
99use crate::{
100    environment_config::NetworkProviderArgs,
101    key_pair::{IdentityFileArgs, PrivateKeyArgs},
102    methods::{
103        deploy_safe_module_with_targets_and_nodes, deregister_nodes_from_node_safe_registry_and_remove_from_module,
104        include_nodes_to_module, migrate_nodes, register_safes_and_nodes_on_network_registry, safe_singleton,
105        transfer_native_tokens, transfer_or_mint_tokens,
106    },
107    utils::{Cmd, HelperErrors},
108};
109use clap::{builder::RangedU64ValueParser, Parser};
110use ethers::{
111    types::{H160, U256},
112    utils::parse_units,
113};
114use hopr_bindings::{
115    hopr_network_registry::HoprNetworkRegistry, hopr_node_safe_registry::HoprNodeSafeRegistry,
116    hopr_node_stake_factory::HoprNodeStakeFactory, hopr_token::HoprToken,
117};
118use hopr_crypto_types::keypairs::Keypair;
119use safe_singleton::SafeSingleton;
120use std::str::FromStr;
121use tracing::{info, warn};
122
123/// CLI arguments for `hopli safe-module`
124#[derive(Clone, Debug, Parser)]
125pub enum SafeModuleSubcommands {
126    /// Create safe and module proxy if nothing exists
127    #[command(visible_alias = "cr")]
128    Create {
129        /// Network name, contracts config file root, and customized provider, if available
130        #[command(flatten)]
131        network_provider: NetworkProviderArgs,
132
133        /// Arguments to locate identity file(s) of HOPR node(s)
134        #[command(flatten)]
135        local_identity: IdentityFileArgs,
136
137        /// node addresses
138        #[clap(
139            help = "Comma separated node Ethereum addresses",
140            long,
141            short = 'o',
142            default_value = None
143        )]
144        node_address: Option<String>,
145
146        /// admin addresses
147        #[clap(
148            help = "Comma separated node Ethereum addresses",
149            long,
150            short = 'a',
151            default_value = None
152        )]
153        admin_address: Option<String>,
154
155        /// Threshold for the generated safe
156        #[clap(
157            help = "Threshold for the generated safe, e.g. 1",
158            long,
159            short,
160            value_parser = RangedU64ValueParser::<u32>::new().range(1..),
161            default_value_t = 1
162        )]
163        threshold: u32,
164
165        /// Allowance of the channel contract to manage HOPR tokens on behalf of deployed safe
166        #[clap(
167            help = "Provide the allowance of the channel contract to manage HOPR tokens on behalf of deployed safe. Value in ether, e.g. 10",
168            long,
169            short = 'l',
170            value_parser = clap::value_parser!(f64),
171        )]
172        allowance: Option<f64>,
173
174        /// The amount of HOPR tokens (in floating number) to be funded to the new safe
175        #[clap(
176            help = "Hopr amount in ether, e.g. 10",
177            long,
178            short = 'm',
179            value_parser = clap::value_parser!(f64),
180        )]
181        hopr_amount: Option<f64>,
182
183        /// The amount of native tokens (in floating number) to be funded per node
184        #[clap(
185            help = "Native token amount in ether, e.g. 1",
186            long,
187            short = 'g',
188            value_parser = clap::value_parser!(f64),
189        )]
190        native_amount: Option<f64>,
191
192        /// Access to the private key, of which the wallet either contains sufficient assets
193        /// as the source of funds or it can mint necessary tokens
194        #[command(flatten)]
195        private_key: PrivateKeyArgs,
196
197        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
198        /// If provided, this wallet will grant the created safe access to the network registry
199        #[command(flatten, name = "manager_private_key")]
200        manager_private_key: ManagerPrivateKeyArgs,
201    },
202
203    /// Migrate safe and module to a new network
204    #[command(visible_alias = "mg")]
205    Migrate {
206        /// Network name, contracts config file root, and customized provider, if available
207        #[command(flatten)]
208        network_provider: NetworkProviderArgs,
209
210        /// Arguments to locate identity file(s) of HOPR node(s)
211        #[command(flatten)]
212        local_identity: IdentityFileArgs,
213
214        /// node addresses
215        #[clap(
216             help = "Comma separated node Ethereum addresses",
217             long,
218             short = 'o',
219             default_value = None
220         )]
221        node_address: Option<String>,
222
223        /// safe address that the nodes move to
224        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
225        safe_address: String,
226
227        /// module address that the nodes move to
228        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
229        module_address: String,
230
231        /// Allowance of the channel contract to manage HOPR tokens on behalf of deployed safe
232        #[clap(
233            help = "Provide the allowance of the channel contract to manage HOPR tokens on behalf of deployed safe. Value in ether, e.g. 10",
234            long,
235            short = 'l',
236            value_parser = clap::value_parser!(f64),
237        )]
238        allowance: Option<f64>,
239
240        /// Access to the private key, of which the wallet either contains sufficient assets
241        /// as the source of funds or it can mint necessary tokens
242        #[command(flatten)]
243        private_key: PrivateKeyArgs,
244
245        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
246        /// If provided, this wallet will grant the created safe access to the network registry
247        #[command(flatten, name = "manager_private_key")]
248        manager_private_key: ManagerPrivateKeyArgs,
249    },
250
251    /// Move nodes to one single safe and module pair
252    #[command(visible_alias = "mv")]
253    Move {
254        /// Network name, contracts config file root, and customized provider, if available
255        #[command(flatten)]
256        network_provider: NetworkProviderArgs,
257
258        /// Arguments to locate identity file(s) of HOPR node(s)
259        #[command(flatten)]
260        local_identity: IdentityFileArgs,
261
262        /// node addresses
263        #[clap(
264             help = "Comma separated node Ethereum addresses",
265             long,
266             short = 'o',
267             default_value = None
268         )]
269        node_address: Option<String>,
270
271        /// old module addresses
272        #[clap(help = "Comma separated old module addresses", long, short = 'u')]
273        old_module_address: String,
274
275        /// safe address that the nodes move to
276        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
277        new_safe_address: String,
278
279        /// module address that the nodes move to
280        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
281        new_module_address: String,
282
283        /// Access to the private key, of which the wallet either contains sufficient assets
284        /// as the source of funds or it can mint necessary tokens
285        #[command(flatten)]
286        private_key: PrivateKeyArgs,
287
288        /// Access to the private key, of which the wallet has `MANAGER_ROLE` of network registry
289        /// If provided, this wallet will grant the created safe access to the network registry
290        #[command(flatten, name = "manager_private_key")]
291        manager_private_key: ManagerPrivateKeyArgs,
292    },
293
294    /// Debug safe and module setup
295    #[command(visible_alias = "dg")]
296    Debug {
297        /// Network name, contracts config file root, and customized provider, if available
298        #[command(flatten)]
299        network_provider: NetworkProviderArgs,
300
301        /// Arguments to locate identity file(s) of HOPR node(s)
302        #[command(flatten)]
303        local_identity: IdentityFileArgs,
304
305        /// node addresses
306        #[clap(
307             help = "Comma separated node Ethereum addresses",
308             long,
309             short = 'o',
310             default_value = None
311         )]
312        node_address: Option<String>,
313
314        /// safe address that the nodes move to
315        #[clap(help = "New managing safe to which all the nodes move", long, short = 's')]
316        safe_address: String,
317
318        /// module address that the nodes move to
319        #[clap(help = "New managing module to which all the nodes move", long, short = 'm')]
320        module_address: String,
321    },
322}
323
324impl SafeModuleSubcommands {
325    /// Execute the command, which quickly create necessary staking wallets
326    /// and execute necessary on-chain transactions to setup a HOPR node.
327    ///
328    /// 1. Create a safe instance and a node management module instance:
329    /// 2. Set default permissions for the module
330    /// 3. Include node as a member with restricted permission on sending assets
331    /// 4. transfer some HOPR token to the new safe (directly)
332    /// 5. transfer some native tokens to nodes
333    #[allow(clippy::too_many_arguments)]
334    pub async fn execute_safe_module_creation(
335        network_provider: NetworkProviderArgs,
336        local_identity: IdentityFileArgs,
337        node_address: Option<String>,
338        admin_address: Option<String>,
339        threshold: u32,
340        allowance: Option<f64>,
341        hopr_amount: Option<f64>,
342        native_amount: Option<f64>,
343        private_key: PrivateKeyArgs,
344        manager_private_key: ManagerPrivateKeyArgs,
345    ) -> Result<(), HelperErrors> {
346        // read all the node addresses
347        let mut node_eth_addresses: Vec<H160> = Vec::new();
348        if let Some(addresses) = node_address {
349            node_eth_addresses.extend(
350                addresses
351                    .split(',')
352                    .map(|addr| {
353                        H160::from_str(addr)
354                            .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
355                    })
356                    .collect::<Result<Vec<_>, _>>()?,
357            );
358        }
359        // if local identity dirs/path is provided, read addresses from identity files
360        node_eth_addresses.extend(
361            local_identity
362                .to_addresses()
363                .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
364                .into_iter()
365                .map(H160::from),
366        );
367
368        let node_addresses = if node_eth_addresses.is_empty() {
369            None
370        } else {
371            Some(node_eth_addresses.clone())
372        };
373
374        // get allowance
375        let token_allowance = match allowance {
376            Some(allw) => U256::from(
377                parse_units(allw, "ether")
378                    .map_err(|_| HelperErrors::ParseError("Failed to parse allowance units".into()))?,
379            ),
380            None => U256::max_value(),
381        };
382
383        // read private key
384        let signer_private_key = private_key.read_default()?;
385        // get RPC provider for the given network and environment
386        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
387        let contract_addresses = network_provider.get_network_details_from_name()?;
388
389        // read all the admin addresses
390        let admin_eth_addresses: Vec<H160> = match admin_address {
391            Some(admin_address_str) => admin_address_str
392                .split(',')
393                .map(|addr| H160::from_str(addr).unwrap())
394                .collect(),
395            None => vec![signer_private_key.clone().public().to_address().into()],
396        };
397
398        // within one multicall, as an owner of the safe
399        // deploy a safe proxy instance and a module proxy instance with multicall as an owner
400        // add announcement as a permitted target in the deployed module proxy
401        // approve token transfer to be done for the safe by channel contracts
402        // if node addresses are known, include nodes to the module by safe
403        // transfer safe ownership to actual admins
404        // set desired threshold
405        let hopr_stake_factory =
406            HoprNodeStakeFactory::new(contract_addresses.addresses.node_stake_v2_factory, rpc_provider.clone());
407
408        let (safe, node_module) = deploy_safe_module_with_targets_and_nodes(
409            hopr_stake_factory,
410            contract_addresses.addresses.token.into(),
411            contract_addresses.addresses.channels.into(),
412            contract_addresses.addresses.module_implementation.into(),
413            contract_addresses.addresses.announcements.into(),
414            token_allowance,
415            node_addresses,
416            admin_eth_addresses,
417            U256::from(threshold),
418        )
419        .await?;
420
421        println!("safe {:?}", safe.address());
422        println!("node_module {:?}", node_module.address());
423
424        // direct transfer of some HOPR tokens to the safe
425        if let Some(hopr_amount_for_safe) = hopr_amount {
426            let hopr_token = HoprToken::new(contract_addresses.addresses.token, rpc_provider.clone());
427            let hopr_to_be_transferred = U256::from(
428                parse_units(hopr_amount_for_safe, "ether")
429                    .map_err(|_| HelperErrors::ParseError("Failed to parse HOPR amount units".into()))?,
430            );
431
432            transfer_or_mint_tokens(hopr_token, vec![safe.address()], vec![hopr_to_be_transferred]).await?;
433            info!(
434                "safe {:?} has received {:?} HOPR tokens",
435                safe.address(),
436                hopr_amount_for_safe
437            );
438        }
439
440        // distribute some native tokens to the nodes
441        if let Some(native_amount_for_node) = native_amount {
442            let native_to_be_transferred = U256::from(
443                parse_units(native_amount_for_node, "ether")
444                    .map_err(|_| HelperErrors::ParseError("Failed to parse HOPR amount units".into()))?,
445            );
446            let native_amounts = vec![native_to_be_transferred; node_eth_addresses.len()];
447            transfer_native_tokens(rpc_provider.clone(), node_eth_addresses.clone(), native_amounts).await?;
448            info!(
449                "each node in {:?} has received {:?} native tokens",
450                node_eth_addresses, native_amount_for_node
451            );
452        }
453
454        // action around network registry
455        if node_eth_addresses.is_empty() {
456            info!("No nodes provided. Skip actions around network registry");
457            return Ok(());
458        }
459
460        // read private key
461        if let Ok(manager_private_key) = manager_private_key.read_default() {
462            // get RPC provider for the given network and environment
463            let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
464            let hopr_network_registry = HoprNetworkRegistry::new(
465                contract_addresses.addresses.network_registry,
466                manager_rpc_provider.clone(),
467            );
468            // Overwrite any past registration of provided nodes in the network registry.
469            // This action is the same as calling `hopli network-registry manager-register`
470            let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
471                hopr_network_registry,
472                vec![safe.address(); node_eth_addresses.len()],
473                node_eth_addresses.clone(),
474            )
475            .await?;
476            info!(
477                "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
478                removed_pairs_num, added_pairs_num
479            );
480        } else {
481            info!("skipping inclusion to the network registry as no manager private key has been provided.");
482        }
483
484        Ok(())
485    }
486
487    /// Execute the command, which moves nodes to a new managing safe and module pair
488    /// Note that it does not register the node with the new safe on NodeSafeRegistry,
489    /// because it is an action that nodes need to do on-start.
490    #[allow(clippy::too_many_arguments)]
491    pub async fn execute_safe_module_moving(
492        network_provider: NetworkProviderArgs,
493        local_identity: IdentityFileArgs,
494        node_address: Option<String>,
495        old_module_address: String,
496        new_safe_address: String,
497        new_module_address: String,
498        private_key: PrivateKeyArgs,
499        manager_private_key: ManagerPrivateKeyArgs,
500    ) -> Result<(), HelperErrors> {
501        // read all the node addresses
502        let mut node_eth_addresses: Vec<H160> = Vec::new();
503        if let Some(addresses) = node_address {
504            node_eth_addresses.extend(
505                addresses
506                    .split(',')
507                    .map(|addr| {
508                        H160::from_str(addr)
509                            .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
510                    })
511                    .collect::<Result<Vec<_>, _>>()?,
512            );
513        }
514        // if local identity dirs/path is provided, read addresses from identity files
515        node_eth_addresses.extend(
516            local_identity
517                .to_addresses()
518                .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
519                .into_iter()
520                .map(H160::from),
521        );
522
523        // parse safe and module addresses
524        let safe_addr = H160::from_str(&new_safe_address)
525            .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", new_safe_address)))?;
526        let module_addr = H160::from_str(&new_module_address).map_err(|_| {
527            HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", new_module_address))
528        })?;
529        let old_module_addr: Vec<H160> = old_module_address
530            .split(',')
531            .map(|addr| H160::from_str(addr).unwrap())
532            .collect();
533
534        // read private key
535        let signer_private_key = private_key.read_default()?;
536        // get RPC provider for the given network and environment
537        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
538        let contract_addresses = network_provider.get_network_details_from_name()?;
539
540        // 1. Deregister the old node-safe from node-safe registry
541        // 2. Remove nodes from the old module
542        // 3. Include node to the new module
543        // 4. Remove node from network registry
544        // 5. Include node to network registry
545        let hopr_node_safe_registry =
546            HoprNodeSafeRegistry::new(contract_addresses.addresses.node_safe_registry, rpc_provider.clone());
547        let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
548
549        if !node_eth_addresses.is_empty() {
550            // first deregister nodes from their old safe
551            match deregister_nodes_from_node_safe_registry_and_remove_from_module(
552                hopr_node_safe_registry.clone(),
553                node_eth_addresses.clone(),
554                old_module_addr,
555                signer_private_key.clone(),
556            )
557            .await
558            {
559                Ok(_) => {
560                    info!("Nodes are deregistered from old safes");
561                }
562                Err(e) => {
563                    return Err(e);
564                }
565            };
566
567            // then include nodes to module
568            match include_nodes_to_module(
569                safe.clone(),
570                node_eth_addresses.clone(),
571                module_addr,
572                signer_private_key,
573            )
574            .await
575            {
576                Ok(_) => {
577                    info!("Nodes are included to the new module");
578                }
579                Err(e) => {
580                    return Err(e);
581                }
582            };
583        };
584
585        // action around network registry
586        if node_eth_addresses.is_empty() {
587            info!("No nodes provided. Skip actions around network registry");
588            return Ok(());
589        }
590
591        // read private key of network registry manager
592        if let Ok(manager_private_key) = manager_private_key.read_default() {
593            // get RPC provider for the given network and environment
594            let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
595            let hopr_network_registry = HoprNetworkRegistry::new(
596                contract_addresses.addresses.network_registry,
597                manager_rpc_provider.clone(),
598            );
599            // Overwrite any past registration of provided nodes in the network registry.
600            // This action is the same as calling `hopli network-registry manager-register`
601            let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
602                hopr_network_registry,
603                vec![safe.address(); node_eth_addresses.len()],
604                node_eth_addresses.clone(),
605            )
606            .await?;
607            info!(
608                "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
609                removed_pairs_num, added_pairs_num
610            );
611        } else {
612            info!("skipping inclusion to the network registry as no manager private key has been provided.");
613        }
614
615        Ok(())
616    }
617
618    /// Execute the command, which migrates nodes to a new network
619    /// Note that it does not register the node with the new safe on NodeSafeRegistry,
620    /// because it is an action that nodes need to do on-start.
621    #[allow(clippy::too_many_arguments)]
622    pub async fn execute_safe_module_migration(
623        network_provider: NetworkProviderArgs,
624        local_identity: IdentityFileArgs,
625        node_address: Option<String>,
626        safe_address: String,
627        module_address: String,
628        allowance: Option<f64>,
629        private_key: PrivateKeyArgs,
630        manager_private_key: ManagerPrivateKeyArgs,
631    ) -> Result<(), HelperErrors> {
632        // read all the node addresses
633        let mut node_eth_addresses: Vec<H160> = Vec::new();
634        if let Some(addresses) = node_address {
635            node_eth_addresses.extend(
636                addresses
637                    .split(',')
638                    .map(|addr| {
639                        H160::from_str(addr)
640                            .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
641                    })
642                    .collect::<Result<Vec<_>, _>>()?,
643            );
644        }
645        // if local identity dirs/path is provided, read addresses from identity files
646        node_eth_addresses.extend(
647            local_identity
648                .to_addresses()
649                .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
650                .into_iter()
651                .map(H160::from),
652        );
653
654        // get allowance
655        let token_allowance = match allowance {
656            Some(allw) => U256::from(
657                parse_units(allw, "ether")
658                    .map_err(|_| HelperErrors::ParseError("Failed to parse allowance units".into()))?,
659            ),
660            None => U256::max_value(),
661        };
662
663        // parse safe and module addresses
664        let safe_addr = H160::from_str(&safe_address)
665            .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", safe_address)))?;
666        let module_addr = H160::from_str(&module_address)
667            .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", module_address)))?;
668
669        // read private key
670        let signer_private_key = private_key.read_default()?;
671        // get RPC provider for the given network and environment
672        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
673        let contract_addresses = network_provider.get_network_details_from_name()?;
674
675        let safe = SafeSingleton::new(safe_addr, rpc_provider.clone());
676
677        // Create a Safe tx to Multisend contract,
678        // 1. scope the Channel contract of the new network to the module as target and set default permissions.
679        // 2. scope the Announcement contract as target to the module
680        // 3. approve HOPR tokens of the Safe proxy to be transferred by the new Channels contract
681        migrate_nodes(
682            safe.clone(),
683            module_addr,
684            contract_addresses.addresses.channels.into(),
685            contract_addresses.addresses.token.into(),
686            contract_addresses.addresses.announcements.into(),
687            token_allowance,
688            signer_private_key,
689        )
690        .await?;
691        info!("a new network has been included due to the migration");
692
693        // action around network registry
694        if node_eth_addresses.is_empty() {
695            info!("No nodes provided. Skip actions around network registry");
696            return Ok(());
697        }
698
699        // read private key of network registry manager
700        if let Ok(manager_private_key) = manager_private_key.read_default() {
701            // get RPC provider for the given network and environment
702            let manager_rpc_provider = network_provider.get_provider_with_signer(&manager_private_key).await?;
703            let hopr_network_registry = HoprNetworkRegistry::new(
704                contract_addresses.addresses.network_registry,
705                manager_rpc_provider.clone(),
706            );
707            // Overwrite any past registration of provided nodes in the network registry.
708            // This action is the same as calling `hopli network-registry manager-register`
709            let (removed_pairs_num, added_pairs_num) = register_safes_and_nodes_on_network_registry(
710                hopr_network_registry,
711                vec![safe.address(); node_eth_addresses.len()],
712                node_eth_addresses.clone(),
713            )
714            .await?;
715            info!(
716                "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
717                removed_pairs_num, added_pairs_num
718            );
719        } else {
720            info!("skipping inclusion to the network registry as no manager private key has been provided.");
721        }
722        Ok(())
723    }
724
725    /// Execute the command to debug the following:
726    /// 1. node xDAI balance
727    /// 2. If node has been included on Network Registry
728    /// 3. If node and safe are associated on Node Safe Registry
729    /// 4. If Safe is owned by the correct owner(s)
730    /// 5. Safe’s wxHOPR balance and allowance
731    /// 6. if node is included in the module
732    /// 7. If the channel contract is included as a target
733    /// 8. If the announce contract is included as a target
734    /// 9. If safe is the owner of the module
735    #[allow(clippy::too_many_arguments)]
736    pub async fn execute_safe_module_debugging(
737        network_provider: NetworkProviderArgs,
738        local_identity: IdentityFileArgs,
739        node_address: Option<String>,
740        safe_address: String,
741        module_address: String,
742    ) -> Result<(), HelperErrors> {
743        // read all the node addresses
744        info!("Reading all the node addresses...");
745        let mut node_eth_addresses: Vec<H160> = Vec::new();
746        if let Some(addresses) = node_address {
747            node_eth_addresses.extend(
748                addresses
749                    .split(',')
750                    .map(|addr| {
751                        H160::from_str(addr)
752                            .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
753                    })
754                    .collect::<Result<Vec<_>, _>>()?,
755            );
756        }
757        // if local identity dirs/path is provided, read addresses from identity files
758        node_eth_addresses.extend(
759            local_identity
760                .to_addresses()
761                .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
762                .into_iter()
763                .map(H160::from),
764        );
765
766        // parse safe and module addresses
767        let safe_addr = H160::from_str(&safe_address)
768            .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse safe address {:?}", safe_address)))?;
769        let module_addr = H160::from_str(&module_address)
770            .map_err(|_| HelperErrors::InvalidAddress(format!("Cannot parse module address {:?}", module_address)))?;
771
772        // get RPC provider for the given network and environment
773        let rpc_provider = network_provider.get_provider_without_signer().await?;
774        let contract_addresses = network_provider.get_network_details_from_name()?;
775
776        let hopr_token = HoprToken::new(contract_addresses.addresses.token, rpc_provider.clone());
777        let network_registry =
778            HoprNetworkRegistry::new(contract_addresses.addresses.network_registry, rpc_provider.clone());
779        let node_safe_registry =
780            HoprNodeSafeRegistry::new(contract_addresses.addresses.node_safe_registry, rpc_provider.clone());
781
782        // loop through all the nodes and debug
783        for node in node_eth_addresses {
784            info!("Starting debug checks for node: {:?}", node);
785            info!("Checking node registration with network registry...");
786            let registered_safe = debug_node_safe_module_setup_on_balance_and_registries(
787                network_registry.clone(),
788                node_safe_registry.clone(),
789                &node,
790            )
791            .await
792            .map_err(|e| {
793                HelperErrors::MulticallError(
794                    format!("failed in getting node balance their registration in network registry and node-safe registry: {:?}", e)
795                )
796            })?;
797
798            // compare the registered safe with the provided safe
799            if registered_safe != safe_addr {
800                warn!(
801                    "Node {:?} is not registered with the provided safe {:?}",
802                    node, safe_addr
803                );
804            }
805            info!("Checking node and safe association in node-safe registry...");
806            debug_node_safe_module_setup_main(
807                hopr_token.clone(),
808                &module_addr,
809                &node,
810                &safe_addr,
811                &contract_addresses.addresses.channels.into(),
812                &contract_addresses.addresses.announcements.into(),
813            )
814            .await
815            .map_err(|e| HelperErrors::MulticallError(format!("failed in debugging safe and module: {:?}", e)))?;
816        }
817        Ok(())
818    }
819}
820
821impl Cmd for SafeModuleSubcommands {
822    /// Run the execute_safe_module_creation function
823    fn run(self) -> Result<(), HelperErrors> {
824        // self.execute_safe_module_creation()
825        Ok(())
826    }
827
828    async fn async_run(self) -> Result<(), HelperErrors> {
829        match self {
830            SafeModuleSubcommands::Create {
831                network_provider,
832                local_identity,
833                node_address,
834                admin_address,
835                threshold,
836                allowance,
837                hopr_amount,
838                native_amount,
839                private_key,
840                manager_private_key,
841            } => {
842                SafeModuleSubcommands::execute_safe_module_creation(
843                    network_provider,
844                    local_identity,
845                    node_address,
846                    admin_address,
847                    threshold,
848                    allowance,
849                    hopr_amount,
850                    native_amount,
851                    private_key,
852                    manager_private_key,
853                )
854                .await
855            }
856            SafeModuleSubcommands::Move {
857                network_provider,
858                local_identity,
859                node_address,
860                old_module_address,
861                new_safe_address,
862                new_module_address,
863                private_key,
864                manager_private_key,
865            } => {
866                SafeModuleSubcommands::execute_safe_module_moving(
867                    network_provider,
868                    local_identity,
869                    node_address,
870                    old_module_address,
871                    new_safe_address,
872                    new_module_address,
873                    private_key,
874                    manager_private_key,
875                )
876                .await
877            }
878            SafeModuleSubcommands::Migrate {
879                network_provider,
880                local_identity,
881                node_address,
882                safe_address,
883                module_address,
884                allowance,
885                private_key,
886                manager_private_key,
887            } => {
888                SafeModuleSubcommands::execute_safe_module_migration(
889                    network_provider,
890                    local_identity,
891                    node_address,
892                    safe_address,
893                    module_address,
894                    allowance,
895                    private_key,
896                    manager_private_key,
897                )
898                .await
899            }
900            SafeModuleSubcommands::Debug {
901                network_provider,
902                local_identity,
903                node_address,
904                safe_address,
905                module_address,
906            } => {
907                SafeModuleSubcommands::execute_safe_module_debugging(
908                    network_provider,
909                    local_identity,
910                    node_address,
911                    safe_address,
912                    module_address,
913                )
914                .await
915            }
916        }
917    }
918}