Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check funder balance is sufficient to fund all accounts before funding any #91

Merged
merged 4 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion crates/cli/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ pub async fn fund_accounts(
let admin_nonce = rpc_client
.get_transaction_count(fund_with.address())
.await?;

// pre-check if admin account has sufficient balance
let gas_price = rpc_client.get_gas_price().await?;
let gas_cost_per_tx = U256::from(21000) * U256::from(gas_price + (gas_price / 10));

let total_cost = U256::from(insufficient_balance_addrs.len()) * (min_balance + gas_cost_per_tx);
if !is_balance_sufficient(&fund_with.address(), total_cost, rpc_client).await? {
return Err(format!(
"Admin account {} has insufficient balance to fund all accounts.",
fund_with.address()
)
.into());
}

for (idx, address) in insufficient_balance_addrs.iter().enumerate() {
if !is_balance_sufficient(&fund_with.address(), min_balance, rpc_client).await? {
// panic early if admin account runs out of funds
Expand All @@ -178,7 +192,7 @@ pub async fn fund_accounts(

let balance = rpc_client.get_balance(*address).await?;
println!(
"Account {} has insufficient balance. (has {}, needed {})",
"Account {} has {}, needed {}",
address,
format_ether(balance),
format_ether(min_balance)
Expand Down Expand Up @@ -290,3 +304,76 @@ pub fn prompt_cli(msg: impl AsRef<str>) -> String {
.expect("Failed to read line");
input.trim().to_owned()
}

#[cfg(test)]
mod test {
use std::str::FromStr;

use alloy::{
consensus::constants::ETH_TO_WEI,
network::AnyNetwork,
node_bindings::{Anvil, AnvilInstance},
primitives::{Address, U256},
providers::{Provider, ProviderBuilder},
signers::local::PrivateKeySigner,
};

use super::fund_accounts;

pub fn spawn_anvil() -> AnvilInstance {
Anvil::new().block_time(1).spawn()
}

#[tokio::test]
async fn fund_accounts_disallows_insufficient_balance() {
let anvil = spawn_anvil();
let rpc_client = ProviderBuilder::new()
.network::<AnyNetwork>()
.on_http(anvil.endpoint_url());
let eth_client = ProviderBuilder::new().on_http(anvil.endpoint_url());
let min_balance = U256::from(ETH_TO_WEI);
let default_signer = PrivateKeySigner::from_str(super::DEFAULT_PRV_KEYS[0]).unwrap();
// address: 0x7E57f00F16dE6A0D6B720E9C0af5C869a1f71c66
let new_signer = PrivateKeySigner::from_str(
"0x08a418b870bf01990abc730a1cfc4ff04811f8e88bafa9edb8d40d802a33891f",
)
.unwrap();
let recipient_addresses: Vec<Address> = [
"0x0000000000000000000000000000000000000013",
"0x7E57f00F16dE6A0D6B720E9C0af5C869a1f71c66",
]
.iter()
.map(|s| s.parse().unwrap())
.collect();

// send eth to the new signer
fund_accounts(
&recipient_addresses,
&default_signer,
&rpc_client,
&eth_client,
min_balance,
)
.await
.unwrap();

for addr in &recipient_addresses {
let balance = rpc_client.get_balance(*addr).await.unwrap();
println!("balance of {}: {}", addr, balance);
assert_eq!(balance, U256::from(ETH_TO_WEI));
}

let res = fund_accounts(
&["0x0000000000000000000000000000000000000014"
.parse::<Address>()
.unwrap()],
&new_signer,
&rpc_client,
&eth_client,
min_balance,
)
.await;
println!("res: {:?}", res);
assert!(res.is_err());
}
}
22 changes: 21 additions & 1 deletion crates/core/src/test_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,10 @@ pub mod tests {
};
use crate::generator::{types::PlanType, util::test::spawn_anvil, RandSeed};
use crate::generator::{Generator, PlanConfig};
use crate::spammer::util::test::get_test_signers;
use crate::spammer::util::test::{fund_account, get_test_signers};
use crate::test_scenario::TestScenario;
use crate::Result;
use alloy::consensus::constants::ETH_TO_WEI;
use alloy::hex::ToHexExt;
use alloy::network::{Ethereum, EthereumWallet, TransactionBuilder};
use alloy::node_bindings::AnvilInstance;
Expand Down Expand Up @@ -851,10 +852,29 @@ pub mod tests {
agents.add_agent("pool2", pool2);
agents.add_agent("admin1", admin1_signers);
agents.add_agent("admin2", admin2_signers);

// fund accounts
let mut nonce = provider
.get_transaction_count(admin.address())
.await
.unwrap();
for (_pool_name, agent) in agents.all_agents() {
for signer in &agent.signers {
let res = fund_account(
&signers[0],
signer.address(),
U256::from(ETH_TO_WEI),
&provider,
Some(nonce),
)
.await
.unwrap();
println!("funded signer: {:?}", res);
provider.watch_pending_transaction(res).await.unwrap();
nonce += 1;
}
}

let chain_id = anvil.chain_id();
for signer in &pool_signers {
let tx = TransactionRequest::default()
Expand Down
Loading