hopli/
faucet.rs

1//! This module contains arguments and functions to fund some Ethereum wallets
2//! with native tokens and HOPR tokens
3//!
4//! Despite HOPR contracts are mainly deployed on Gnosis chain,
5//! HOPR token contract addresses vary on the network.
6//!
7//! Attention! Do not use this function to distribute large amount of tokens
8//!
9//! Note that to save gas in batch funding, multicall is used to facilitate token distribution, via `transferFrom`
10//! To use this functionality, caller must grant Multicall3 contract the exact allowance equal to the sum of tokens
11//! to be transferred. As it's a separate function, there is a window between granting the allowance and executing
12//! the transactin. Attacker may take advantage of this window and steal tokens from the caller's account.
13//!
14//! A sample command:
15//! ```text
16//! hopli faucet \
17//!     --network anvil-localhost \
18//!     --contracts-root "../ethereum/contracts" \
19//!     --address 0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1,0xd057604a14982fe8d88c5fc25aac3267ea142a08 \
20//!     --identity-directory "./test" --identity-prefix node_ \
21//!     --password-path "./test/pwd" \
22//!     --private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
23//!     --hopr-amount 10 --native-amount 0.1 \
24//!     --provider-url "http://localhost:8545"
25//! ```
26use crate::{
27    environment_config::NetworkProviderArgs,
28    key_pair::{ArgEnvReader, IdentityFileArgs, PrivateKeyArgs},
29    methods::{get_native_and_token_balances, transfer_native_tokens, transfer_or_mint_tokens},
30    utils::{Cmd, HelperErrors},
31};
32use clap::Parser;
33use ethers::{
34    types::{H160, U256},
35    utils::parse_units,
36};
37use hopr_bindings::hopr_token::HoprToken;
38// use ethers::types::Address;
39use std::{ops::Sub, str::FromStr};
40use tracing::info;
41
42/// CLI arguments for `hopli faucet`
43#[derive(Parser, Default, Debug)]
44pub struct FaucetArgs {
45    /// Network name, contracts config file root, and customized provider, if available
46    #[clap(flatten)]
47    pub network_provider: NetworkProviderArgs,
48
49    /// Additional addresses (comma-separated) to receive funds.
50    #[clap(
51        help = "Comma-separated Ethereum addresses of nodes that will receive funds",
52        long,
53        short,
54        default_value = None
55    )]
56    address: Option<String>,
57
58    /// Argument to locate identity file(s)
59    #[clap(flatten)]
60    local_identity: IdentityFileArgs,
61
62    /// The amount of HOPR tokens (in floating number) to be funded per wallet
63    #[clap(
64        help = "Hopr amount in ether, e.g. 10",
65        long,
66        short = 't',
67        value_parser = clap::value_parser!(f64),
68        default_value_t = 2000.0
69    )]
70    hopr_amount: f64,
71
72    /// The amount of native tokens (in floating number) to be funded per wallet
73    #[clap(
74        help = "Native token amount in ether, e.g. 1",
75        long,
76        short = 'g',
77        value_parser = clap::value_parser!(f64),
78        default_value_t = 10.0
79    )]
80    native_amount: f64,
81
82    /// Access to the private key, of which the wallet either contains sufficient assets
83    /// as the source of funds or it can mint necessary tokens
84    #[clap(flatten)]
85    pub private_key: PrivateKeyArgs,
86}
87
88impl FaucetArgs {
89    /// Execute the faucet command, which funds addresses with required amount of tokens
90    async fn execute_faucet(self) -> Result<(), HelperErrors> {
91        let FaucetArgs {
92            network_provider,
93            address,
94            local_identity,
95            hopr_amount,
96            native_amount,
97            private_key,
98        } = self;
99
100        // Include provided address
101        let mut eth_addresses_all: Vec<H160> = Vec::new();
102        if let Some(addresses) = address {
103            eth_addresses_all.extend(addresses.split(',').map(|addr| {
104                H160::from_str(addr).unwrap_or_else(|e| {
105                    panic!(
106                        "{}",
107                        format!("Cannot parse address {:?} for eth_addresses_all, due to {:?}", addr, e)
108                    )
109                })
110            }));
111        }
112        // if local identity dirs/path is provided, read addresses from identity files
113        eth_addresses_all.extend(local_identity.to_addresses()?.into_iter().map(H160::from));
114        info!("All the addresses: {:?}", eth_addresses_all);
115
116        // `PRIVATE_KEY` - Private key is required to send on-chain transactions
117        let signer_private_key = private_key.read_default()?;
118
119        // get RPC provider for the given network and environment
120        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
121        let contract_addresses = network_provider.get_network_details_from_name()?;
122
123        let hopr_token = HoprToken::new(contract_addresses.addresses.token, rpc_provider.clone());
124
125        // complete actions as defined in `transferOrMintHoprAndSendNativeToAmount` in `SingleActions.s.sol`
126        // get token and native balances for addresses
127        let (native_balances, token_balances) =
128            get_native_and_token_balances(hopr_token.clone(), eth_addresses_all.clone())
129                .await
130                .map_err(|_| HelperErrors::MulticallError("cannot get native and token balances".into()))?;
131        // Get the amount of HOPR tokens that addresses need to receive to reach the desired amount
132        let hopr_token_amounts: Vec<U256> = vec![
133            U256::from(parse_units(hopr_amount, "ether").map_err(|_| {
134                HelperErrors::ParseError("Failed to parse hopr_amount units".into())
135            })?);
136            eth_addresses_all.len()
137        ]
138        .into_iter()
139        .enumerate()
140        .map(|(i, h)| {
141            if h.gt(&token_balances[i]) {
142                let diff = h.sub(token_balances[i]);
143                info!(
144                    "{:?} hopr-token to be transferred to {:?} to top up {:?}",
145                    diff, eth_addresses_all[i], &token_balances[i]
146                );
147                diff
148            } else {
149                U256::zero()
150            }
151        })
152        .collect();
153
154        // Get the amount of native tokens that addresses need to receive to reach the desired amount
155        let native_token_amounts: Vec<U256> = vec![
156            U256::from(parse_units(native_amount, "ether").map_err(|_| {
157                HelperErrors::ParseError("Failed to parse native_amount units".into())
158            })?);
159            eth_addresses_all.len()
160        ]
161        .into_iter()
162        .enumerate()
163        .map(|(i, n)| {
164            if n.gt(&native_balances[i]) {
165                let diff = n.sub(native_balances[i]);
166                info!(
167                    "{:?} native-token to be transferred to {:?}, to top up {:?}",
168                    diff, eth_addresses_all[i], &native_balances[i]
169                );
170                diff
171            } else {
172                U256::zero()
173            }
174        })
175        .collect();
176
177        // transfer of mint HOPR tokens
178        let total_transferred_hopr_token =
179            transfer_or_mint_tokens(hopr_token, eth_addresses_all.clone(), hopr_token_amounts).await?;
180        info!("total transferred hopr-token is {:?}", total_transferred_hopr_token);
181        // send native tokens
182        let total_transferred_native_token =
183            transfer_native_tokens(rpc_provider, eth_addresses_all, native_token_amounts).await?;
184        info!("total transferred native-token is {:?}", total_transferred_native_token);
185        Ok(())
186    }
187}
188
189impl Cmd for FaucetArgs {
190    /// Run the execute_faucet function
191    fn run(self) -> Result<(), HelperErrors> {
192        Ok(())
193    }
194
195    async fn async_run(self) -> Result<(), HelperErrors> {
196        self.execute_faucet().await
197    }
198}