Skip to content
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
56 changes: 17 additions & 39 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ Generate key and deposit data with a new mnemonic:
./target/debug/eth-staking-smith new-mnemonic --chain mainnet --keystore_password testtest --num_validators 1
```

### EIP-7251 Type 2 Compounding Validators

Generate validators with variable deposits up to 2048 ETH.

```
# Default 32 ETH compounding validator
./target/debug/eth-staking-smith new-mnemonic --chain mainnet --keystore_password testtest --num_validators 1 --compounding

# Type 2 compounding validator with 100 ETH
./target/debug/eth-staking-smith new-mnemonic --chain mainnet --keystore_password testtest --num_validators 1 --deposit_amount_eth 100 --compounding

# Maximum 2048 ETH compounding validator
./target/debug/eth-staking-smith new-mnemonic --chain mainnet --keystore_password testtest --num_validators 1 --deposit_amount_eth 2048 --compounding

# Legacy 32 ETH validator with BLS withdrawal credentials
./target/debug/eth-staking-smith new-mnemonic --chain mainnet --keystore_password testtest --num_validators 1 --withdrawal_credentials "0x00abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456"
```

## Existing mnemonic

Regenerate key and deposit data with existing mnemonic:
Expand Down
4 changes: 3 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ ignore = [
"RUSTSEC-2024-0388",
# lighthouse depends on paste
"RUSTSEC-2024-0436",
# httpmock will replace async-std in 0.8.0
"RUSTSEC-2025-0052",
]

[sources]
Expand Down Expand Up @@ -33,7 +35,7 @@ allow = [
#
# Most of crates above are coming from lighthouse
# where they keep repository license of Apache 2.0
# https://github.com/sigp/lighthouse/blob/291146eeb4fea4bbe0aa3c6aa37eadd566d7e1d4/LICENSE
# https://github.com/sigp/lighthouse/blob/291146eeb4fea4bbe0aa3c6aa37eadd566d7e1d4/LICENSE
[[licenses.clarify]]
crate = "merkle_proof"
expression = "Apache-2.0"
Expand Down
31 changes: 23 additions & 8 deletions src/cli/existing_mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ pub struct ExistingMnemonicSubcommandOpts {
pub validator_start_index: Option<u32>,

/// If this field is set and valid, the given
/// value will be used to set the
/// withdrawal credentials. Otherwise, it will
/// generate withdrawal credentials with the
/// mnemonic-derived withdrawal public key. Valid formats are
/// ^(0x[a-fA-F0-9]{40})$ for execution addresses,
/// ^(0x01[0]{22}[a-fA-F0-9]{40})$ for execution withdrawal credentials
/// value will be used to set the withdrawal credentials.
/// When --compounding is specified, execution addresses and 0x01
/// credentials will be converted to 0x02 compounding credentials.
/// Valid formats are ^(0x[a-fA-F0-9]{40})$ for execution addresses,
/// ^(0x01[0]{22}[a-fA-F0-9]{40})$ for execution withdrawal credentials,
/// ^(0x02[a-fA-F0-9]{62})$ for EIP-7251 compounding withdrawal credentials (supports variable deposits up to 2048 ETH),
/// and ^(0x00[a-fA-F0-9]{62})$ for BLS withdrawal credentials.
#[arg(long, visible_alias = "withdrawal_credentials")]
pub withdrawal_credentials: Option<String>,
Expand All @@ -70,6 +70,20 @@ pub struct ExistingMnemonicSubcommandOpts {
/// A version of CLI to include into generated deposit data
#[arg(long, visible_alias = "deposit_cli_version", default_value = "2.7.0")]
pub deposit_cli_version: String,

/// Deposit amount in ETH.
/// For standard validators: exactly 32 ETH.
/// For EIP-7251 compounding validators (0x02 withdrawal credentials): 32 to 2048 ETH.
#[arg(long, visible_alias = "deposit_amount", default_value = "32")]
pub deposit_amount_eth: u64,

/// Use EIP-7251 compounding withdrawal credentials (0x02).
///
/// When enabled, validators will use 0x02 withdrawal credentials which support
/// compounding rewards and variable deposit amounts up to 2048 ETH.
/// When disabled, validators use traditional 0x00 BLS withdrawal credentials.
#[arg(long)]
pub compounding: bool,
}

impl ExistingMnemonicSubcommandOpts {
Expand All @@ -93,14 +107,15 @@ impl ExistingMnemonicSubcommandOpts {
password,
Some(self.num_validators),
self.validator_start_index,
self.withdrawal_credentials.is_none(),
self.withdrawal_credentials.is_none() || self.compounding,
self.kdf.clone(),
);
let export: serde_json::Value = validators
.export(
chain,
self.withdrawal_credentials.clone(),
32_000_000_000,
self.deposit_amount_eth * 1_000_000_000, // Convert ETH to Gwei
self.compounding,
self.deposit_cli_version.clone(),
self.testnet_config.clone(),
)
Expand Down
31 changes: 23 additions & 8 deletions src/cli/new_mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ pub struct NewMnemonicSubcommandOpts {
pub validator_start_index: Option<u32>,

/// If this field is set and valid, the given
/// value will be used to set the
/// withdrawal credentials. Otherwise, it will
/// generate withdrawal credentials with the
/// mnemonic-derived withdrawal public key. Valid formats are
/// ^(0x[a-fA-F0-9]{40})$ for execution addresses,
/// ^(0x01[0]{22}[a-fA-F0-9]{40})$ for execution withdrawal credentials
/// value will be used to set the withdrawal credentials.
/// When --compounding is specified, execution addresses and 0x01
/// credentials will be converted to 0x02 compounding credentials.
/// Valid formats are ^(0x[a-fA-F0-9]{40})$ for execution addresses,
/// ^(0x01[0]{22}[a-fA-F0-9]{40})$ for execution withdrawal credentials,
/// ^(0x02[a-fA-F0-9]{62})$ for EIP-7251 compounding withdrawal credentials (supports variable deposits up to 2048 ETH),
/// and ^(0x00[a-fA-F0-9]{62})$ for BLS withdrawal credentials.
#[arg(long, visible_alias = "withdrawal_credentials")]
pub withdrawal_credentials: Option<String>,
Expand All @@ -59,6 +59,20 @@ pub struct NewMnemonicSubcommandOpts {
/// A version of CLI to include into generated deposit data
#[arg(long, visible_alias = "deposit_cli_version", default_value = "2.7.0")]
pub deposit_cli_version: String,

/// Deposit amount in ETH.
/// For standard validators: exactly 32 ETH.
/// For EIP-7251 compounding validators (0x02 withdrawal credentials): 32 to 2048 ETH.
#[arg(long, visible_alias = "deposit_amount", default_value = "32")]
pub deposit_amount_eth: u64,

/// Use EIP-7251 compounding withdrawal credentials (0x02).
///
/// When enabled, validators will use 0x02 withdrawal credentials which support
/// compounding rewards and variable deposit amounts up to 2048 ETH.
/// When disabled, validators use traditional 0x00 BLS withdrawal credentials.
#[arg(long)]
pub compounding: bool,
}

impl NewMnemonicSubcommandOpts {
Expand All @@ -82,14 +96,15 @@ impl NewMnemonicSubcommandOpts {
password,
Some(self.num_validators),
None,
self.withdrawal_credentials.is_none(),
self.withdrawal_credentials.is_none() || self.compounding,
self.kdf.clone(),
);
let export: serde_json::Value = validators
.export(
chain,
self.withdrawal_credentials.clone(),
32_000_000_000,
self.deposit_amount_eth * 1_000_000_000, // Convert ETH to Gwei
self.compounding,
self.deposit_cli_version.clone(),
self.testnet_config.clone(),
)
Expand Down
14 changes: 8 additions & 6 deletions src/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
chain_spec::{chain_spec_for_network, chain_spec_from_file},
key_material::VotingKeyMaterial,
networks::SupportedNetworks,
utils::{is_compounding_withdrawal_credentials, validate_deposit_amount},
};

#[derive(Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -36,12 +37,13 @@ pub(crate) fn keystore_to_deposit(
));
};

// For simplicity, support only 32Eth deposits
if deposit_amount_gwei != 32_000_000_000 {
return Err(DepositError::InvalidDepositAmount(
"Invalid amount of deposit data, should be 32Eth".to_string(),
));
};
// Validate deposit amount based on withdrawal credentials type
let withdrawal_creds_hex = hex::encode(withdrawal_credentials);
let is_compounding = is_compounding_withdrawal_credentials(&withdrawal_creds_hex);

if let Err(msg) = validate_deposit_amount(deposit_amount_gwei, is_compounding) {
return Err(DepositError::InvalidDepositAmount(msg));
}

let spec = match network {
Some(chain) => chain_spec_for_network(&chain)?,
Expand Down
52 changes: 52 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,63 @@ pub fn withdrawal_creds_from_pk(withdrawal_pk: &PublicKeyBytes) -> String {
hex::encode(withdrawal_creds)
}

/// Creates 0x02 compounding withdrawal credentials from BLS public key (EIP-7251)
///
/// Used for type 2 validators that support compounding rewards up to 2048 ETH
pub fn compounding_withdrawal_creds_from_pk(withdrawal_pk: &PublicKeyBytes) -> String {
let withdrawal_creds = get_withdrawal_credentials(withdrawal_pk, 2);
hex::encode(withdrawal_creds)
}

/// Validates deposit amount according to Ethereum staking rules
///
/// Post-Pectra (EIP-7251): 32 ETH to 2048 ETH for compounding validators
/// Pre-Pectra: Only 32 ETH allowed
pub fn validate_deposit_amount(amount_gwei: u64, is_compounding: bool) -> Result<(), String> {
const MIN_DEPOSIT_GWEI: u64 = 32_000_000_000; // 32 ETH
const MAX_EFFECTIVE_BALANCE: u64 = 2_048_000_000_000; // 2048 ETH

if amount_gwei < MIN_DEPOSIT_GWEI {
return Err(format!(
"Deposit amount must be at least 32 ETH (got {} Gwei)",
amount_gwei
));
}

if is_compounding {
if amount_gwei > MAX_EFFECTIVE_BALANCE {
return Err(format!(
"Compounding validator deposit amount cannot exceed 2048 ETH (got {} Gwei)",
amount_gwei
));
}
} else if amount_gwei != MIN_DEPOSIT_GWEI {
return Err(format!(
"Non-compounding validator deposit amount must be exactly 32 ETH (got {} Gwei)",
amount_gwei
));
}

Ok(())
}

/// Determines if withdrawal credentials indicate a compounding validator (0x02 prefix)
pub fn is_compounding_withdrawal_credentials(withdrawal_credentials: &str) -> bool {
let creds = if let Some(stripped) = withdrawal_credentials.strip_prefix("0x") {
stripped
} else {
withdrawal_credentials
};
creds.starts_with("02")
}

// Various regexes used for input validation
lazy_static::lazy_static! {
/// see format of execution address: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#eth1_address_withdrawal_prefix
pub static ref EXECUTION_ADDR_REGEX: Regex = Regex::new(r"^(0x[a-fA-F0-9]{40})$").unwrap();
pub static ref EXECUTION_CREDS_REGEX: Regex =
Regex::new(r"^(0x01[0]{22}[a-fA-F0-9]{40})$").unwrap();
pub static ref BLS_CREDS_REGEX: Regex = Regex::new(r"^(0x00[a-fA-F0-9]{62})$").unwrap();
/// EIP-7251 compounding withdrawal credentials pattern
pub static ref COMPOUNDING_CREDS_REGEX: Regex = Regex::new(r"^(0x02[a-fA-F0-9]{62})$").unwrap();
}
Loading
Loading