Files
lighthouse/common/deposit_contract/src/lib.rs
Mac L f3fd1f210b Remove consensus/types re-exports (#8540)
There are certain crates which we re-export within `types` which creates a fragmented DevEx, where there are various ways to import the same crates.

```rust
// consensus/types/src/lib.rs
pub use bls::{
AggregatePublicKey, AggregateSignature, Error as BlsError, Keypair, PUBLIC_KEY_BYTES_LEN,
PublicKey, PublicKeyBytes, SIGNATURE_BYTES_LEN, SecretKey, Signature, SignatureBytes,
get_withdrawal_credentials,
};
pub use context_deserialize::{ContextDeserialize, context_deserialize};
pub use fixed_bytes::FixedBytesExtended;
pub use milhouse::{self, List, Vector};
pub use ssz_types::{BitList, BitVector, FixedVector, VariableList, typenum, typenum::Unsigned};
pub use superstruct::superstruct;
```

This PR removes these re-exports and makes it explicit that these types are imported from a non-`consensus/types` crate.


Co-Authored-By: Mac L <mjladson@pm.me>
2025-12-09 07:13:41 +00:00

171 lines
5.0 KiB
Rust

use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
use alloy_json_abi::JsonAbi;
use alloy_primitives::FixedBytes;
use bls::{PublicKeyBytes, SignatureBytes};
use ssz::{Decode, DecodeError as SszDecodeError, Encode};
use tree_hash::TreeHash;
use types::{DepositData, Hash256};
#[derive(Debug)]
pub enum Error {
AlloyCoreError(alloy_json_abi::Error),
SerdeJsonError(serde_json::Error),
DynAbiError(alloy_dyn_abi::Error),
SszDecodeError(SszDecodeError),
FunctionNotFound,
MissingField,
UnableToGetBytes,
MissingToken,
InadequateBytes,
}
impl From<alloy_json_abi::Error> for Error {
fn from(e: alloy_json_abi::Error) -> Error {
Error::AlloyCoreError(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Error {
Error::SerdeJsonError(e)
}
}
impl From<alloy_dyn_abi::Error> for Error {
fn from(e: alloy_dyn_abi::Error) -> Error {
Error::DynAbiError(e)
}
}
impl From<SszDecodeError> for Error {
fn from(e: SszDecodeError) -> Error {
Error::SszDecodeError(e)
}
}
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 400_000;
pub const ABI: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/v0.12.1_validator_registration.json"
));
pub const BYTECODE: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/v0.12.1_validator_registration.bytecode"
));
pub const DEPOSIT_DATA_LEN: usize = 420; // lol
pub mod testnet {
pub const ABI: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/v0.12.1_testnet_validator_registration.json"
));
pub const BYTECODE: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/v0.12.1_testnet_validator_registration.bytecode"
));
}
pub fn encode_eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
let params = vec![
DynSolValue::Bytes(deposit_data.pubkey.as_ssz_bytes()),
DynSolValue::Bytes(deposit_data.withdrawal_credentials.as_ssz_bytes()),
DynSolValue::Bytes(deposit_data.signature.as_ssz_bytes()),
DynSolValue::FixedBytes(
FixedBytes::<32>::from_slice(&deposit_data.tree_hash_root().as_ssz_bytes()),
32,
),
];
// Here we make an assumption that the `crate::testnet::ABI` has a superset of the features of
// the crate::ABI`.
let abi: JsonAbi = serde_json::from_slice(ABI)?;
let function = abi
.function("deposit")
.and_then(|functions| functions.first())
.ok_or(Error::FunctionNotFound)?;
function
.abi_encode_input(&params)
.map_err(Error::DynAbiError)
}
pub fn decode_eth1_tx_data(bytes: &[u8], amount: u64) -> Result<(DepositData, Hash256), Error> {
let abi: JsonAbi = serde_json::from_slice(ABI)?;
let function = abi
.function("deposit")
.and_then(|functions| functions.first())
.ok_or(Error::FunctionNotFound)?;
let input_data = bytes.get(4..).ok_or(Error::InadequateBytes)?;
let mut tokens = function.abi_decode_input(input_data)?;
macro_rules! decode_token {
($type: ty) => {{
let token = tokens.pop().ok_or(Error::MissingToken)?;
let bytes_data = match token {
DynSolValue::Bytes(b) => b,
DynSolValue::FixedBytes(b, _) => b.to_vec(),
_ => return Err(Error::UnableToGetBytes),
};
<$type>::from_ssz_bytes(&bytes_data)?
}};
}
let root = decode_token!(Hash256);
let deposit_data = DepositData {
amount,
signature: decode_token!(SignatureBytes),
withdrawal_credentials: decode_token!(Hash256),
pubkey: decode_token!(PublicKeyBytes),
};
Ok((deposit_data, root))
}
#[cfg(test)]
mod tests {
use super::*;
use bls::{Keypair, Signature};
use types::{ChainSpec, EthSpec, MinimalEthSpec, test_utils::generate_deterministic_keypair};
type E = MinimalEthSpec;
fn get_deposit(keypair: Keypair, spec: &ChainSpec) -> DepositData {
let mut deposit_data = DepositData {
pubkey: keypair.pk.into(),
withdrawal_credentials: Hash256::from_slice(&[42; 32]),
amount: u64::MAX,
signature: Signature::empty().into(),
};
deposit_data.signature = deposit_data.create_signature(&keypair.sk, spec);
deposit_data
}
#[test]
fn round_trip() {
let spec = &E::default_spec();
let keypair = generate_deterministic_keypair(42);
let original = get_deposit(keypair, spec);
let data = encode_eth1_tx_data(&original).expect("should produce tx data");
assert_eq!(
data.len(),
DEPOSIT_DATA_LEN,
"bytes should be correct length"
);
let (decoded, root) = decode_eth1_tx_data(&data, original.amount).expect("should decode");
assert_eq!(decoded, original, "decoded should match original");
assert_eq!(
root,
original.tree_hash_root(),
"decode root should match original root"
);
}
}