use alloy::primitives::B256;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
#[serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockGasPrice {
pub block_number: u64,
pub block_hash: B256,
pub block_timestamp: u64,
#[serde(flatten)]
pub pricing: GasPrice,
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GasPrice {
Legacy {
#[serde_as(as = "DisplayFromStr")]
gas_price: BigUint,
},
Eip1559 {
#[serde_as(as = "DisplayFromStr")]
base_fee_per_gas: BigUint,
#[serde_as(as = "DisplayFromStr")]
max_priority_fee_per_gas: BigUint,
},
}
impl BlockGasPrice {
pub fn effective_gas_price(&self) -> BigUint {
match &self.pricing {
GasPrice::Legacy { gas_price } => gas_price.clone(),
GasPrice::Eip1559 { base_fee_per_gas, max_priority_fee_per_gas } => {
base_fee_per_gas + max_priority_fee_per_gas
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_legacy_effective_gas_price() {
let gas_price_value = BigUint::from(50_000_000_000u64); let legacy = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Legacy { gas_price: gas_price_value.clone() },
};
assert_eq!(legacy.effective_gas_price(), gas_price_value);
}
#[test]
fn test_eip1559_effective_gas_price() {
let base_fee = BigUint::from(30_000_000_000u64); let priority_fee = BigUint::from(2_000_000_000u64); let expected = base_fee.clone() + priority_fee.clone();
let eip1559 = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Eip1559 {
base_fee_per_gas: base_fee,
max_priority_fee_per_gas: priority_fee,
},
};
assert_eq!(eip1559.effective_gas_price(), expected);
}
#[test]
fn test_serialize_legacy_gas_price() {
let gas_price = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Legacy { gas_price: BigUint::from(50_000_000_000u64) },
};
let json = serde_json::to_string(&gas_price).unwrap();
assert_eq!(
json,
r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"legacy","gas_price":"50000000000"}"#
);
}
#[test]
fn test_deserialize_legacy_gas_price() {
let json = r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"legacy","gas_price":"50000000000"}"#;
let gas_price: BlockGasPrice = serde_json::from_str(json).unwrap();
assert_eq!(
gas_price,
BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Legacy { gas_price: BigUint::from(50_000_000_000u64) },
}
);
}
#[test]
fn test_serialize_eip1559_gas_price() {
let gas_price = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Eip1559 {
base_fee_per_gas: BigUint::from(30_000_000_000u64),
max_priority_fee_per_gas: BigUint::from(2_000_000_000u64),
},
};
let json = serde_json::to_string(&gas_price).unwrap();
assert_eq!(
json,
r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"eip1559","base_fee_per_gas":"30000000000","max_priority_fee_per_gas":"2000000000"}"#
);
}
#[test]
fn test_deserialize_eip1559_gas_price() {
let json = r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"eip1559","base_fee_per_gas":"30000000000","max_priority_fee_per_gas":"2000000000"}"#;
let gas_price: BlockGasPrice = serde_json::from_str(json).unwrap();
assert_eq!(
gas_price,
BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Eip1559 {
base_fee_per_gas: BigUint::from(30_000_000_000u64),
max_priority_fee_per_gas: BigUint::from(2_000_000_000u64),
},
}
);
}
#[test]
fn test_roundtrip_legacy() {
let original = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Legacy { gas_price: BigUint::from(123_456_789_000u64) },
};
let json = serde_json::to_string(&original).unwrap();
let deserialized: BlockGasPrice = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_roundtrip_eip1559() {
let original = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Eip1559 {
base_fee_per_gas: BigUint::from(987_654_321_000u64),
max_priority_fee_per_gas: BigUint::from(5_000_000_000u64),
},
};
let json = serde_json::to_string(&original).unwrap();
let deserialized: BlockGasPrice = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_serialize_large_numbers() {
let large_value = BigUint::parse_bytes(b"1000000000000000000000", 10).unwrap();
let gas_price = BlockGasPrice {
block_number: 12345,
block_hash: B256::ZERO,
block_timestamp: 1234567890,
pricing: GasPrice::Legacy { gas_price: large_value },
};
let json = serde_json::to_string(&gas_price).unwrap();
let deserialized: BlockGasPrice = serde_json::from_str(&json).unwrap();
assert_eq!(gas_price, deserialized);
}
#[test]
fn test_deserialize_invalid_json() {
let invalid_json = r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"legacy","gas_price":"not_a_number"}"#;
let result: Result<BlockGasPrice, _> = serde_json::from_str(invalid_json);
assert!(result.is_err());
}
#[test]
fn test_deserialize_missing_field() {
let invalid_json = r#"{"block_number":12345,"block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","block_timestamp":1234567890,"type":"eip1559","base_fee_per_gas":"30000000000"}"#;
let result: Result<BlockGasPrice, _> = serde_json::from_str(invalid_json);
assert!(result.is_err());
}
}