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
}
}