hopli/
faucet.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! This module contains arguments and functions to fund some Ethereum wallets
//! with native tokens and HOPR tokens
//!
//! Despite HOPR contracts are mainly deployed on Gnosis chain,
//! HOPR token contract addresses vary on the network.
//!
//! Attention! Do not use this function to distribute large amount of tokens
//!
//! Note that to save gas in batch funding, multicall is used to facilitate token distribution, via `transferFrom`
//! To use this functionality, caller must grant Multicall3 contract the exact allowance equal to the sum of tokens
//! to be transferred. As it's a separate function, there is a window between granting the allowance and executing
//! the transactin. Attacker may take advantage of this window and steal tokens from the caller's account.
//!
//! A sample command:
//! ```text
//! hopli faucet \
//!     --network anvil-localhost \
//!     --contracts-root "../ethereum/contracts" \
//!     --address 0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1,0xd057604a14982fe8d88c5fc25aac3267ea142a08 \
//!     --identity-directory "./test" --identity-prefix node_ \
//!     --password-path "./test/pwd" \
//!     --private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
//!     --hopr-amount 10 --native-amount 0.1 \
//!     --provider-url "http://localhost:8545"
//! ```
use crate::{
    environment_config::NetworkProviderArgs,
    key_pair::{ArgEnvReader, IdentityFileArgs, PrivateKeyArgs},
    methods::{get_native_and_token_balances, transfer_native_tokens, transfer_or_mint_tokens},
    utils::{Cmd, HelperErrors},
};
use clap::Parser;
use ethers::{
    types::{H160, U256},
    utils::parse_units,
};
use hopr_bindings::hopr_token::HoprToken;
// use ethers::types::Address;
use std::{ops::Sub, str::FromStr};
use tracing::info;

/// CLI arguments for `hopli faucet`
#[derive(Parser, Default, Debug)]
pub struct FaucetArgs {
    /// Network name, contracts config file root, and customized provider, if available
    #[clap(flatten)]
    pub network_provider: NetworkProviderArgs,

    /// Additional addresses (comma-separated) to receive funds.
    #[clap(
        help = "Comma-separated Ethereum addresses of nodes that will receive funds",
        long,
        short,
        default_value = None
    )]
    address: Option<String>,

    /// Argument to locate identity file(s)
    #[clap(flatten)]
    local_identity: IdentityFileArgs,

    /// The amount of HOPR tokens (in floating number) to be funded per wallet
    #[clap(
        help = "Hopr amount in ether, e.g. 10",
        long,
        short = 't',
        value_parser = clap::value_parser!(f64),
        default_value_t = 2000.0
    )]
    hopr_amount: f64,

    /// The amount of native tokens (in floating number) to be funded per wallet
    #[clap(
        help = "Native token amount in ether, e.g. 1",
        long,
        short = 'g',
        value_parser = clap::value_parser!(f64),
        default_value_t = 10.0
    )]
    native_amount: f64,

    /// Access to the private key, of which the wallet either contains sufficient assets
    /// as the source of funds or it can mint necessary tokens
    #[clap(flatten)]
    pub private_key: PrivateKeyArgs,
}

impl FaucetArgs {
    /// Execute the faucet command, which funds addresses with required amount of tokens
    async fn execute_faucet(self) -> Result<(), HelperErrors> {
        let FaucetArgs {
            network_provider,
            address,
            local_identity,
            hopr_amount,
            native_amount,
            private_key,
        } = self;

        // Include provided address
        let mut eth_addresses_all: Vec<H160> = Vec::new();
        if let Some(addresses) = address {
            eth_addresses_all.extend(addresses.split(',').map(|addr| {
                H160::from_str(addr).unwrap_or_else(|e| {
                    panic!(
                        "{}",
                        format!("Cannot parse address {:?} for eth_addresses_all, due to {:?}", addr, e)
                    )
                })
            }));
        }
        // if local identity dirs/path is provided, read addresses from identity files
        eth_addresses_all.extend(local_identity.to_addresses()?.into_iter().map(H160::from));
        info!("All the addresses: {:?}", eth_addresses_all);

        // `PRIVATE_KEY` - Private key is required to send on-chain transactions
        let signer_private_key = private_key.read_default()?;

        // get RPC provider for the given network and environment
        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
        let contract_addresses = network_provider.get_network_details_from_name()?;

        let hopr_token = HoprToken::new(contract_addresses.addresses.token, rpc_provider.clone());

        // complete actions as defined in `transferOrMintHoprAndSendNativeToAmount` in `SingleActions.s.sol`
        // get token and native balances for addresses
        let (native_balances, token_balances) =
            get_native_and_token_balances(hopr_token.clone(), eth_addresses_all.clone())
                .await
                .map_err(|_| HelperErrors::MulticallError("cannot get native and token balances".into()))?;
        // Get the amount of HOPR tokens that addresses need to receive to reach the desired amount
        let hopr_token_amounts: Vec<U256> = vec![
            U256::from(parse_units(hopr_amount, "ether").map_err(|_| {
                HelperErrors::ParseError("Failed to parse hopr_amount units".into())
            })?);
            eth_addresses_all.len()
        ]
        .into_iter()
        .enumerate()
        .map(|(i, h)| {
            if h.gt(&token_balances[i]) {
                let diff = h.sub(token_balances[i]);
                info!(
                    "{:?} hopr-token to be transferred to {:?} to top up {:?}",
                    diff, eth_addresses_all[i], &token_balances[i]
                );
                diff
            } else {
                U256::zero()
            }
        })
        .collect();

        // Get the amount of native tokens that addresses need to receive to reach the desired amount
        let native_token_amounts: Vec<U256> = vec![
            U256::from(parse_units(native_amount, "ether").map_err(|_| {
                HelperErrors::ParseError("Failed to parse native_amount units".into())
            })?);
            eth_addresses_all.len()
        ]
        .into_iter()
        .enumerate()
        .map(|(i, n)| {
            if n.gt(&native_balances[i]) {
                let diff = n.sub(native_balances[i]);
                info!(
                    "{:?} native-token to be transferred to {:?}, to top up {:?}",
                    diff, eth_addresses_all[i], &native_balances[i]
                );
                diff
            } else {
                U256::zero()
            }
        })
        .collect();

        // transfer of mint HOPR tokens
        let total_transferred_hopr_token =
            transfer_or_mint_tokens(hopr_token, eth_addresses_all.clone(), hopr_token_amounts).await?;
        info!("total transferred hopr-token is {:?}", total_transferred_hopr_token);
        // send native tokens
        let total_transferred_native_token =
            transfer_native_tokens(rpc_provider, eth_addresses_all, native_token_amounts).await?;
        info!("total transferred native-token is {:?}", total_transferred_native_token);
        Ok(())
    }
}

impl Cmd for FaucetArgs {
    /// Run the execute_faucet function
    fn run(self) -> Result<(), HelperErrors> {
        Ok(())
    }

    async fn async_run(self) -> Result<(), HelperErrors> {
        self.execute_faucet().await
    }
}