diff --git a/CHANGELOG.md b/CHANGELOG.md index 1687a9c..d42e784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added +- Added bidirectional conversion between `scalar::BigInt` and `substreams_ethereum::pb::eth::v2::BigInt` via the `From` trait +- Added new optional `ethereum` feature to enable Ethereum-specific functionality + ## 0.6.1 - Substreams `map` or `store` input that starts with `_` doesn't generate a warning about snake cases not being respected. diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml index f250e4f..9fd9963 100644 --- a/substreams/Cargo.toml +++ b/substreams/Cargo.toml @@ -26,6 +26,11 @@ substreams-macro = { workspace = true } thiserror = "1" pest= "2.7.10" pest_derive = "2.7.10" +substreams-ethereum = { version = "0.10", optional = true } + +[features] +default = [] +ethereum = ["substreams-ethereum"] [dev-dependencies] rstest = "0.19.0" diff --git a/substreams/src/conversions.rs b/substreams/src/conversions.rs new file mode 100644 index 0000000..3511252 --- /dev/null +++ b/substreams/src/conversions.rs @@ -0,0 +1,76 @@ +/// This module contains conversion traits between different BigInt implementations. +/// +/// Currently, it provides conversions between: +/// - `substreams::scalar::BigInt` (aka `scalar::BigInt`) +/// - `substreams_ethereum::pb::eth::v2::BigInt` (aka `pb::BigInt`) +/// +/// The conversions are implemented using the `From` and `Into` traits, which allows +/// for seamless conversion between the different types. +/// +/// Note: This module is only available when the `ethereum` feature is enabled. +/// +/// # Examples +/// +/// ```rust +/// # #[cfg(feature = "ethereum")] +/// # { +/// use substreams::scalar::BigInt as ScalarBigInt; +/// use substreams_ethereum::pb::eth::v2::BigInt as PbBigInt; +/// +/// // Convert from scalar::BigInt to pb::eth::v2::BigInt +/// let scalar_bigint = ScalarBigInt::from(42); +/// let pb_bigint: PbBigInt = scalar_bigint.into(); +/// +/// // Convert from pb::eth::v2::BigInt to scalar::BigInt +/// let pb_bigint = PbBigInt { bytes: vec![42] }; +/// let scalar_bigint: ScalarBigInt = pb_bigint.into(); +/// # } +/// ``` + +/// This trait is implemented for the Ethereum BigInt type to convert from scalar::BigInt. +/// +/// Example: +/// ```rust +/// # #[cfg(feature = "ethereum")] +/// # { +/// use substreams::scalar::BigInt as ScalarBigInt; +/// use substreams_ethereum::pb::eth::v2::BigInt as PbBigInt; +/// +/// let scalar_bigint = ScalarBigInt::from(42); +/// let pb_bigint: PbBigInt = scalar_bigint.into(); +/// # } +/// ``` +#[cfg(feature = "ethereum")] +impl From for substreams_ethereum::pb::eth::v2::BigInt { + fn from(value: crate::scalar::BigInt) -> Self { + // Convert scalar::BigInt to pb::eth::v2::BigInt + // The Ethereum BigInt uses bytes representation + let bytes = value.to_signed_bytes_be(); + substreams_ethereum::pb::eth::v2::BigInt { bytes } + } +} + +/// This trait is implemented for the scalar::BigInt type to convert from Ethereum BigInt. +/// +/// Example: +/// ```rust +/// # #[cfg(feature = "ethereum")] +/// # { +/// use substreams::scalar::BigInt as ScalarBigInt; +/// use substreams_ethereum::pb::eth::v2::BigInt as PbBigInt; +/// +/// let pb_bigint = PbBigInt { bytes: vec![0x01] }; +/// let scalar_bigint: ScalarBigInt = pb_bigint.into(); +/// # } +/// ``` +#[cfg(feature = "ethereum")] +impl From for crate::scalar::BigInt { + fn from(value: substreams_ethereum::pb::eth::v2::BigInt) -> Self { + // Convert pb::eth::v2::BigInt to scalar::BigInt + // The scalar::BigInt can be created from signed big-endian bytes + crate::scalar::BigInt::from_signed_bytes_be(&value.bytes) + } +} + +#[cfg(test)] +mod tests; diff --git a/substreams/src/conversions_test.rs b/substreams/src/conversions_test.rs new file mode 100644 index 0000000..1b733b4 --- /dev/null +++ b/substreams/src/conversions_test.rs @@ -0,0 +1,65 @@ +#[cfg(test)] +#[cfg(feature = "ethereum")] +mod tests { + use crate::scalar::BigInt as ScalarBigInt; + use substreams_ethereum::pb::eth::v2::BigInt as PbBigInt; + + #[test] + fn test_scalar_bigint_to_pb_bigint() { + // Test conversion from scalar::BigInt to pb::eth::v2::BigInt + let scalar_bigint = ScalarBigInt::from(42); + let pb_bigint: PbBigInt = scalar_bigint.into(); + + // The Ethereum BigInt uses big-endian signed bytes + assert_eq!(pb_bigint.bytes, vec![42]); + + // Test with a larger number + let scalar_bigint = ScalarBigInt::from(256); + let pb_bigint: PbBigInt = scalar_bigint.into(); + assert_eq!(pb_bigint.bytes, vec![1, 0]); + + // Test with a negative number + let scalar_bigint = ScalarBigInt::from(-42); + let pb_bigint: PbBigInt = scalar_bigint.into(); + // In two's complement, -42 in a single byte would be 214 (256 - 42) + assert_eq!(pb_bigint.bytes, vec![214]); + } + + #[test] + fn test_pb_bigint_to_scalar_bigint() { + // Test conversion from pb::eth::v2::BigInt to scalar::BigInt + let pb_bigint = PbBigInt { bytes: vec![42] }; + let scalar_bigint: ScalarBigInt = pb_bigint.into(); + + // Convert back to a value we can check + assert_eq!(scalar_bigint.to_string(), "42"); + + // Test with a larger number + let pb_bigint = PbBigInt { bytes: vec![1, 0] }; + let scalar_bigint: ScalarBigInt = pb_bigint.into(); + assert_eq!(scalar_bigint.to_string(), "256"); + + // Test with a negative number (in two's complement) + let pb_bigint = PbBigInt { bytes: vec![214] }; // -42 in two's complement + let scalar_bigint: ScalarBigInt = pb_bigint.into(); + assert_eq!(scalar_bigint.to_string(), "-42"); + } + + #[test] + fn test_roundtrip_conversion() { + // Test roundtrip conversion: scalar::BigInt -> pb::eth::v2::BigInt -> scalar::BigInt + let original = ScalarBigInt::from(12345); + let pb_bigint: PbBigInt = original.clone().into(); + let roundtrip: ScalarBigInt = pb_bigint.into(); + + assert_eq!(original.to_string(), roundtrip.to_string()); + + // Test with a negative number + let original = ScalarBigInt::from(-12345); + let pb_bigint: PbBigInt = original.clone().into(); + let roundtrip: ScalarBigInt = pb_bigint.into(); + + assert_eq!(original.to_string(), roundtrip.to_string()); + } +} + diff --git a/substreams/src/lib.rs b/substreams/src/lib.rs index c4a3f8c..dc7fffc 100644 --- a/substreams/src/lib.rs +++ b/substreams/src/lib.rs @@ -121,6 +121,10 @@ pub mod pb; pub mod proto; pub mod scalar; +/// Conversions between different BigInt implementations +#[cfg(feature = "ethereum")] +pub mod conversions; + mod state; pub mod key;